Getting connection-string using Dependency Injection in OnConfiguring method in console application? - c#

In a .NET Core Console Application I have the following DbContext:
public class AppDataContext : DbContext
{
public DbSet<ExampleObject> ExampleObjects { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(/* Read Connection String from appsettings.json */);
}
}
Of course I could instantiate the ConfigurationBuilder class and access the connectionString, like this:
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
var connString = Configuration.GetConnectionString("data"));
but how can I force Entity Framework to use the IConfigurationRoot instance defined with dependency injection?
Thank you

You're approaching it from the wrong angle. You're currently trying to "bring in" configuration. What you need to be doing is "applying" the configuration at the ServiceCollection level so that the Net Core Dependency Injection can instantiate your DbContext with the correct values automatically. Here is an example of how to do that:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BloggingContext>(options => options.UseSqlite("Data Source=blog.db"));
}
Of course you can still use the IConfigurationBuilder to get your connection string - that would go in the ConfigureServices section above and "Data Source=blog.db" would be swapped for your value
Your DbContext can then be simplified like this:
public class AppDataContext : DbContext
{
public DbSet<ExampleObject> ExampleObjects { get; set; }
}

Related

Dependency Injection with configuration in .net core

I am trying to inject dependency by getting configuration in class in .net core project. The class where I am trying to inject dependency is in another project. But somehow I am not able to get the values from config file in injected dependency.
Below is my code
In below DBContext I need to obtain value from configuration, where i have used DI of DBConfiguration class.
public class DBContext : DbContext
{
private readonly DBConfiguration _dBConfiguration;
public DBContext(DBConfiguration dBConfiguration)
{
_dBConfiguration = dBConfiguration;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_dBConfiguration.ConnectionString);
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
And my StartUp.cs file in web api
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<DBConfiguration>();
services.AddEntityFrameworkSqlServer().AddDbContext<DBContext>();
services.AddOptions();
services.Configure<DBConfiguration>(Configuration.GetSection("DBConfiguration"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
and my appsettings.json file
{
"DBConfiguration": {
"ConnectionString": "Server=myserver;Database=BaseProjectDB;Trusted_Connection=True;MultipleActiveResultSets=true",
"ApplicationName": "WebAPI"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
Any help on this appreciated !
You seem to be using DBConfigurationOptions in your StartUp file, while you're injecting DBConfiguration in your DBContext.
This is how I currently use my configuration:
public class Startup
{
private readonly IConfiguration _configuration;
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
_configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<DBConfigurationOptions>(_configuration.GetSection("DBConfiguration"));
}
}
and then it's injected as:
public class DBContext : DbContext
{
private readonly DBConfigurationOptions _dBConfiguration;
public DBContext(IOptions<DBConfigurationOptions> dBConfiguration)
{
_dBConfiguration = dBConfiguration.Value;
}
}
Why don't you configure the db directly in the place you also have the configuration?
In your DBContext class (btw you should probably choose a better name for that) you just need to expose a constructor like this, no need for overriding OnConfiguring or anything like that.
This class can be in any assembly you want.
class DBContext : DbContext
{
public DBContext(DbContextOptions<DBContext> options) : base(options)
{
}
}
For the configuration you can just use the built in optionsBuilder-action (place inside the ConfigureServices method):
services.AddEntityFrameworkSqlServer()
.AddDbContext<DBContext>(optionsBuilder =>
optionsBuilder.UseSqlServer(
Configuration.GetSection("DBConfiguration").Get<DBConfiguration>().ConnectionString)
);
Currently the way you get the configuration can definitely be improved. For example you could do something like this:
var DBConfig = Configuration.GetSection("DBConfiguration").Get<DBConfiguration>();
services.AddSingleton(DBConfig);// <- now you can inject that if you want but it's not necessary
// now we don't need to get the config here
services.AddEntityFrameworkSqlServer()
.AddDbContext<DBContext>(optionsBuilder =>
optionsBuilder.UseSqlServer(DBConfig.ConnectionString)
);
There are some other things you might want to improve like better naming for DBContext and not overriding members you don't have a specific implementation for (like you did with OnModelCreating).
Also for a next time you should include all the classes that aren't part of some sort of public API like your DBConfiguration class.
To receive the configuration, you have to modify the signature of your constructor in DBContext from
public DBContext(DBConfiguration dBConfiguration)
to
public DBContext(IOptions<DBConfiguration> dBConfiguration)
to receive the option properly (don't forget to add the namespace Microsoft.Extensions.Options).
Additionally, you need to define the DBConfiguration class somewhere with the properties you have in your configuration file.

Adding connection string to DBContext (NpgSQL, Dependency Injection, .NET Core)

I am trying dynamically load up a connection string and inject it in to my DbContext inherited class. I am not sure I am doing it correctly (can't get it to work anyway).
My MyDbContext looks like this:
public class MyDbContext : DbContext
{
private readonly string _connectionString;
public DbSet<Things> Things{ get; set; }
public MyDbContext (string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(_connectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ForNpgsqlUseIdentityColumns();
}
}
My Startup, looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFrameworkNpgsql()
.AddDbContext<MyDbContext>(s => new MyDbContext("Host=192.168.0.1; Port=4016;Database=Test;Username=test;Password=test"))
.BuildServiceProvider();
services.AddMvc();
services.AddTransient<DbContext, MyDbContext>(s => new MyDbContext("Host=192.168.0.1; Port=4016;Database=Test;Username=test;Password=test"));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetRequiredService<MyDbContext>();
context.Database.Migrate();
}
}
The code throws an exception on the line within Configure(...):
var context = serviceScope.ServiceProvider.GetRequiredService<MyDbContext>();
The exception is:
Application startup exception: System.InvalidOperationException: Unable to resolve service for type 'System.String' while attempting to activate 'MyDbContext'.
EF Core has two ways for initializing a DbContext - via dependency injection or without it. You're using dependency injection, so your DbContext needs to provide a constructor that accepts DbContextOptions<TContext> (see this doc link, as #ivan-stoev wrote above).
A constructor accepting a string is only used outside of dependency injection.

Add-Migration fails with Dependency injection in console core 2.0 app

I try to use EF Core 2.0 in a console .net core 2.0 application. When i either use IDesignTimeDbContextFactory or override OnConfiguring method of a DbContext derived class CustomerDbContext, then Add-Migration is successfull in package manager console. But if i try to use AddDbContext like in an asp.net core application, i receive the message:
Unable to create an object of type 'CustomerDbContext'
The code snippet for console .net core application is:
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
IConfigurationRoot config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("config.json") //needs Microsoft.Extensions.Configuration.Json
.Build();
string conn = config.GetConnectionString("WarehouseConnection");
services
.AddDbContext<CustomerDbContext>(
options => options.UseSqlServer(config.GetConnectionString("WarehouseConnection")));
var provider = services.BuildServiceProvider();
using(var context = provider.GetService<CustomerDbContext>())
{
...
}
}
}
the config.json contains the connection string:
{
"ConnectionStrings": {
"WarehouseConnection": "Server=(localdb)\\mssqllocaldb;Database=WAREHOUSE;Trusted_Connection=True;MultipleActiveResultSets=true"
}
the CustomerDbContext class is:
class CustomerDbContext : DbContext
{
public CustomerDbContext(DbContextOptions<CustomerDbContext> options) : base(options) { }
public DbSet<Customer> Customers { get; set; }
}
Any ideas why this approach fails?
Starting from 2.0, the EF tool checks for a BuildWebHost() method and uses it to access application services if it exsits. Since you don't need a BuildWebHost() using IDesignTimeDbContextFactory is the right approach.
Check the announcement and this comment in the discussion for more information.
So you need to implement IDesignTimeDbContextFactory interface. Add a class that implements this interface inside of your project.
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
public CustomerDbContext CreateDbContext(string[] args)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<CustomerDbContext>();
var connectionString = configuration.GetConnectionString("WarehouseConnection");
builder.UseSqlServer(connectionString);
return new CustomerDbContext(builder.Options);
}
}
For more info read Cannot make migration. ef core 2.0

Passing application's connection string down to a Repository Class Library in ASP.NET 5 using the IConfigurationRoot

I have an ASP.NET 5 MVC Web Application and in Startup.cs I see that the public property
IConfigurationRoot Configuration
is being set to
builder.Build();
Throughout the MVC Web Application I can simply do
Startup.Configuration["Data:DefaultConnection:ConnectionString"]
to get the conn string from the appsettings.json file.
How can I get the connection string specified in the ASP.NET 5 MVC appsettings.json passed down to my Repository Class Library using constructor injection?
UPDATE:
Here is the base repository that all other repositories inherit from (as you can see I have a hardcoded connection string in here for now):
public class BaseRepo
{
public static string ConnectionString = "Server=MYSERVER;Database=MYDATABASE;Trusted_Connection=True;";
public static SqlConnection GetOpenConnection()
{
var cs = ConnectionString;
var connection = new SqlConnection(cs);
connection.Open();
return connection;
}
}
In my asp.net 5 web application in my appsettings.json file I have the following which is equivalent to adding a connection string to a web.config in a .net 4.5 webapp:
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=MYSERVER;Database=MYDATABASE;Trusted_Connection=True;"
}
}
Additionally in my asp.net 5 web application I have the following default code in my Startup.cs which loads the sites configuration into a public property of type IConfigurationRoot:
public IConfigurationRoot Configuration { get; set; }
// Class Constructor
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
Now in my asp.net web application if I would like to access any of the appsettings I can simple do the following: Startup.Configuration["Data:DefaultConnection:ConnectionString"]
But unfortunately I can't do this from my class library..
If someone wants to try and figure this out here are the steps to reproduce:
Create a new ASP.NET 5 MVC Web App.
Add another project of type Class Library (Package) to the project.
Figure out a way to pass appsettings from the ASP.NET 5 MVC App to the Class Library
After updating I still can't quite get it. Here is my code:
public class BaseRepo
{
private readonly IConfigurationRoot config;
public BaseRepo(IConfigurationRoot config)
{
this.config = config;
}
}
This class declaration does not work since BaseRepo requires a constructor param now.
public class CustomerRepo : BaseRepository, ICustomerRepo
{
public Customer Find(int id)
{
using (var connection = GetOpenConnection())
{
...
}
}
}
on your Startup.cs file add the following method
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton(_ => Configuration);
}
then update your BaseRepo class like this
public class BaseRepo {
private readonly IConfiguration config;
public BaseRepo(IConfiguration config) {
this.config = config;
}
public SqlConnection GetOpenConnection() {
string cs = config["Data:DefaultConnection:ConnectionString"];
SqlConnection connection = new SqlConnection(cs);
connection.Open();
return connection;
}
}
ASP.NET provides its own way of passing around configuration settings.
Suppose you have the this in your appSettings.json:
{
"Config": {
"Setting1": 1,
"Setting2": "SO"
}
}
Then you need a class like this:
public class MyConfiguration
{
public int Setting1 { get; set; }
public string Setting2 { get; set; }
}
This allows you to configure your service with this configuration by adding the following line
services.Configure<MyConfigurationDto>(Configuration.GetSection("Config"));
to ConfigureServices.
You can then inject the configuration in constructors by doing the following:
public class SomeController : Controller
{
private readonly IOptions<MyConfiguration> config;
public ServiceLocatorController(IOptions<MyConfiguration> config)
{
this.config = config;
}
[HttpGet]
public IActionResult Get()
{
return new HttpOkObjectResult(config.Value);
}
}
This example is for controllers. But you can do the same with other layers of you application.
I have a constructor in my repository class that accepts the db connection string as a parameter. This works for me when I add my repository for injection. In ConfigureServies() of the startup.cs file add this:
services.AddScoped<IRepos>(c => new Repos(Configuration["DbConnections:ConnStr1"]));
IRepos.cs is the interface, Repos.cs is the class that implements it. And of course Configuration is just a reference to the built IConfigurationRoot object.
A slightly different approach would be to make a static class in your Class Library on which you call a method from the Configure(..)-method in Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
ConnectionManager.SetConfig(Configuration);
}
In this case, I've added Configuration as a Singleton in ConfigureServices:
services.AddSingleton(_ => Configuration);
My ConnectionManager looks like this:
public class ConnectionManager
{
private static IConfiguration currentConfig;
public static void SetConfig(IConfiguration configuration)
{
currentConfig = configuration;
}
/// <summary>
/// Get a connection to the database.
/// </summary>
public static SqlConnection GetConnection
{
get
{
string connectionString = currentConfig.GetConnectionString("MyConnection");
// Create a new connection for each query.
SqlConnection connection = new SqlConnection(connectionString);
return connection;
}
}
}
This may or may not have some issues regarding object lifetimes and such, and I'm certainly no fan of static classes but as far as I can tell it's a viable approach. Instead of passing Configuration you could even extract the ConnectionString from the config-file and send only that.
There is already an extension method you can use to get connection strings specifically from aspsettings.json.
Define your connection strings in appsettings.json like this:
{
"ConnectionStrings": {
"Local": "Data source=.\\SQLExpress;Initial Catalog=.......",
"Test:": "Data source=your-server;......"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
In your public void ConfigureServices(IServiceCollection services) method inside your Startup.cs, you can get the connection string like this:
var connectionString = this.Configuration.GetConnectionString("Local");
The GetConnectionString extension is from Microsoft.Extensions.Configuration.
Enjoy :)
UPDATE
I didn't want to go into details at first because the question here had already been marked as answered. But I guess I can show my 2 cents here.
If you do need the whole IConfigurationRoot object injected into Controllers, #Chrono showed the right way.
If you don't need the whole object, you should just get the connection string, pass it into the DbContext inside the ConfigureServices() call, and inject the DbContext into Controllers. #Prashant Lakhlani showed it correctly.
I am just saying, in #Prashant Lakhlani post, you can use GetConnectionString extension instead to clean up the code a little bit.
If ConfigureServices in your project's startUp.cs contains
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<YourDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
and your repository cs file is having constructor injection as shown below
public class MyRepo : IRepo
{
private readonly YourDbContext dbContext;
public MyRepo(YourDbContext ctx)
{
dbContext = ctx;
}
}
YourDbContext will be automatically resolved.
What you need is to create a class in class library project to access the appsettings.json in website project and return connection string.
{
private static string _connectionString;
public static string GetConnectionString()
{
if (string.IsNullOrEmpty(_connectionString))
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
_connectionString = Configuration.Get<string>("Data:MyDb:ConnectionString");
}
return _connectionString;
}
public static IConfigurationRoot Configuration { get; set; }
}

How do you configure the DbContext when creating Migrations in Entity Framework Core?

Is there way that dependency injection can be configured/bootstrapped when using Entity Framework's migration commands?
Entity Framework Core supports dependency injection for DbContext subclasses. This mechanism includes allowing for configuration of data access outside of of the DbContext.
For example, the following would configure EF to persist to a SQL server using a connection string retrieved from config.json
ServiceCollection services = ...
var configuration = new Configuration().AddJsonFile( "config.json" );
services.AddEntityFramework( configuration )
.AddSqlServer()
.AddDbContext<BillingDbContext>( config => config.UseSqlServer() );
However, the migrations commands do not know to execute this code so Add-Migration will fail for lack of a provider or lack of a connection string.
Migrations can be made to work by overriding OnConfiguring within the DbContext subclass to specify the provider and configuration string, but that gets in the way when different configuration is desired elsewhere. Ultimately keeping my the migration commands and my code both working becomes undesirably complex.
Note: My DbContext lives in a different assembly than the entry point that uses it and my solution has multiple start-up projects.
If you are looking for a solution to configure context for migrations, you can use this in your DBContext class:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var connectionString = configuration.GetConnectionString("DbCoreConnectionString");
optionsBuilder.UseSqlServer(connectionString);
}
}
Remember to install those two packages to have SetBasePath and AddJsonFile methods:
Microsoft.Extensions.Configuration.FileExtensions
Microsoft.Extensions.Configuration.Json
Use IDesignTimeDbContextFactory
If a class implementing this interface is found in either the same project as the derived DbContext or in the application's startup project, the tools bypass the other ways of creating the DbContext and use the design-time factory instead.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace MyProject
{
public class BloggingContextFactory : IDesignTimeDbContextFactory<BloggingContext>
{
public BloggingContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlite("Data Source=blog.db");
return new BloggingContext(optionsBuilder.Options);
}
}
}
applied in Entity Framework 2.0, 2.1
Using IDbContextFactory<TContext> is now obsolete.
Implement this interface to enable design-time services for context types that do not have a public default constructor. Design-time services will automatically discover implementations of this interface that are in the same assembly as the derived context.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace MyProject
{
public class BloggingContextFactory : IDbContextFactory<BloggingContext>
{
public BloggingContext Create()
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer("connection_string");
return new BloggingContext(optionsBuilder.Options);
}
}
}
more info : https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext
If you're not happy with the hard-coded connection-string, take a look at this article.
As #bricelam commented this functionality does not yet exist in Entity Framework 7. This missing functionality is tracked by GitHub issue aspnet/EntityFramework#639
In the mean time, the easier workaround I found was to utilize a global state rather than hassle with subclassing. Not usually my first design choice but it works well for now.
In MyDbContext:
public static bool isMigration = true;
protected override void OnConfiguring( DbContextOptionsBuilder optionsBuilder )
{
// TODO: This is messy, but needed for migrations.
// See https://github.com/aspnet/EntityFramework/issues/639
if ( isMigration )
{
optionsBuilder.UseSqlServer( "<Your Connection String Here>" );
}
}
In Startup.ConfigureServices().
public IServiceProvider ConfigureServices( IServiceCollection services )
{
MyContext.isMigration = false;
var configuration = new Configuration().AddJsonFile( "config.json" );
services.AddEntityFramework( configuration )
.AddSqlServer()
.AddDbContext<MyDbContext>( config => config.UseSqlServer() );
// ...
}
(The configuration code actually lives in an Autofac Module in my case.)
In .NET Core since version 2.1 should be used IDesignTimeDbContextFactory because IDbContextFactory is obsolete.
public class FooDbContextFactory : IDesignTimeDbContextFactory<FooDbContext>
{
public FooDbContext CreateDbContext(string[] args)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<FooDbContext>();
var connectionString = configuration.GetConnectionString("ConnectionStringName");
builder.UseSqlServer(connectionString);
return new FooDbContext(builder.Options);
}
}
To combine the answers above this works for me
private readonly bool isMigration = false;
public MyContext()
{
isMigration = true;
}
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (isMigration)
{
optionsBuilder.UseSqlServer("CONNECTION_STRING");
}
}
I know this is a old question but I use the onConfiguring method and I don't have this problem
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(Startup.Configuration.Get("Data:DefaultConnection:ConnectionString"));
}
I just ask for an instance and run migrations in my Startup.cs file
public void ConfigureServices(IServiceCollection services)
{
// ASPNet Core Identity
services.AddDbContext<RRIdentityDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("RRIdentityConnectionString")));
}
And then in Configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var rrIdentityContext = app.ApplicationServices.GetService<RRIdentityDbContext>();
rrIdentityContext.Database.Migrate();
}
Note: There is no 'EnsureCreated' for the database. Migrate is supposed to create it if it doesn't exist, although how it is supposed to figure out the permissions I don't know - so I created an empty database.

Categories

Resources