Using Configuration Settings in ASP.NET 5 - c#

I am learning about ASP.NET 5. One of the areas that has me really confused is configuration. For the life of me, I cannot figure out how to load a value from the configuration file in a class. Before ASP.NET 5, I would just use:
ConfigurationManager.AppSettings["KeyName"];
However, now with ASP.NET 5, I'm a little confused. Currently, I have the following in Startup.cs:
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment environment)
{
var configuration = new Configuration()
.AddJsonFile("config.json");
Configuration = configuration;
}
This seems to load the configuration settings just fine. However, lets say I have a class called "Customer.cs" which interacts with the database. The database connection string is in config.json. How do I get that value? It doesn't seem efficient to load "config.json" in all of my POCOs. At the same time, the approach above doesn't seem to allow me to access Configuration in a global manner.
How should I load configuration settings and retrieve the configuration values in my POCOs?
Thank you

You should not load the connection string in each POCO entity (Have you ever used a DB Context?). If you're using Entity Framework 7 you can load the database context service when your app starts.
If your config.json looks like this:
"Data": {
"DefaultConnection": {
"Connectionstring": "your_connection_string"
}
you can set up your environment like this:
public void ConfigureServices(IServiceCollection services)
{
// Add EF services to the services container
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<Your__DB__Context__Class>(options =>
options.UseSqlServer(Configuration.Get("Data:DefaultConnection:ConnectionString")));
}
//... other services...
}
Then in your controller you can directly use the DbContext through dependency injection:
public class Your__Controller : Controller
{
[FromServices]
public Your__DB__Context__Class DbContext { get; set; }
//...
}
If you need to use the DbContext service outside from the controller you have to do like this:
class NotAControllerClass : NotAController
{
private readonly Your__DB__Context__Class _dbContext;
public NotAControllerClass(Your__DB__Context__Class DbContext)
{
_dbContext = DbContext;
}
//do stuff here...
}
Finally if you're not using EF7 but instead a prevoious version of EF or a custom ORM you need a custom dependency injection. Try to search it on the internet.

Related

Need a way to get the current request URL to configure the database context in multi-tenant application

I am migrating a web app from asp.net mvc to .net core (.net 5), and this has got me stuck.
The site is configured in IIS to accept request from multiple URLs like site1.example.com and site2.example.com. Each site has its own database, accessed through entity framework core.
In the old .net framework, I was able to use one of the events in the global.asax.cs to parse the incoming request URL and lookup the correct tenant database from a configuration file. I'm trying to set up something similar in asp.net core mvc.
Here's the relevant part of my ConfigureServices method in the startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddSingleton<ITenantIdentifier, UrlTenantIdentifier>();
services.AddDbContext<myDbContext>((serviceProvider, dbContextBuilder) =>
{
var tenantIdentifier = serviceProvider.GetRequiredService<ITenantIdentifier>();
var connectionString = Configuration.GetConnectionString(tenantIdentifier.GetCurrentTenantId() + "myDataModel");
dbContextBuilder.UseSqlServer(connectionString);
}, ServiceLifetime.Scoped);
//other services configured below...
}
Then the tenant identifier looks like this:
public interface ITenantIdentifier
{
string GetCurrentTenantId();
}
public class UrlTenantIdentifier : ITenantIdentifier
{
readonly IHttpContextAccessor _httpContextAccessor;
readonly ILogger<UrlTenantIdentifier> _logger;
public UrlTenantIdentifier(IHttpContextAccessor httpContextAccessor, ILogger<UrlTenantIdentifier> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public string GetCurrentTenantId()
{
//_httpContextAccessor is null here
//logic below for parsing URL and finding if we're site1 or site2
}
}
Is there a correct way of doing this now that I'm not aware of? How can I set up the entity framework database context for dependency injection when I don't know the connection string key until runtime? Am I going to be stuck configuring separate sites and virtual directories in IIS?
Refactor the DbContext to override the OnConfiguring member. Inject configuration and context accessor and perform configuration there.
public class myDbContext : DbContext {
private readonly ITenantIdentifier tenantIdentifier;
private readonly IConfiguration configuration;
public myDbContext(IConfiguration configuration, ITenantIdentifier tenantIdentifier) {
this.configuration = configuration;
this.tenantIdentifier = tenantIdentifier;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
var connectionString = configuration
.GetConnectionString(tenantIdentifier.GetCurrentTenantId() + "myDataModel");
optionsBuilder.UseSqlServer(connectionString);
}
}
Trying to access the request context at the time the DbContext is being created/initialized is too early in the request flow to get access to the desired information. It needs to happen after the context has already been initialized and injected.
public void ConfigureServices(IServiceCollection services)
services.AddHttpContextAccessor();
services.AddSingleton<ITenantIdentifier, UrlTenantIdentifier>();
services.AddDbContext<myDbContext>(); //Simplified since configuration is internal
//other services configured below...
}
Reference DbContext Lifetime, Configuration, and Initialization

How to read tables in asp.net core, with database first approach

So, I created a new database and a table in SQLEXPRESS, I also filled tables with random information, and I used key word in Nuget console -> Scaffold-DbContext "Server=.\SQLExpress;Database=testdb;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models. The Program successfully created models, so I just wanted to output the information, so I created a new apicontroller, with [HttpGet], and injected the created testdbcontext which was created to get information about the database tables, so In a word I created the following method.
[HttpGet("All")]
public IActionResult GetAll()
{
var obj = _db.Mobiletables.ToList();
return Ok(obj);
}
However, when I go to localhost/home/all I get the following error
What am I doing wrong, I just want to read information from the database to api, using DB First Approach method.
Your controller code looks correct, but based off of your comment of "I have not added anything in startup services" you are missing something like this in Startup.cs (specifically the AddDbContext line):
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SchoolContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllersWithViews();
}
https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro?view=aspnetcore-5.0
You still need to set up your context so that it can be used when it is injected.
Your _db need to be initialized in your class constructor
public YOURController(YOURDbContext con)
{
_db= con;
}
If you Connection String is in appsettings.json add this to startup.cs inside ConfigureServices
services.AddDbContext<YOURDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("connstr")));

Cannot inject DbContext in repository

I try to setup the DI for a new ASP.NET Core site and I have this code:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Get the configuration from the app settings.
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
// Get app settings to configure things accordingly.
var appSettings = Configuration.GetSection("AppSettings");
var settings = new AppSettings();
appSettings.Bind(settings);
services
.AddOptions()
.Configure<AppSettings>(appSettings)
.AddSingleton<IConfigurationRoot>(config)
.AddDbContext<MyDbContext>(builder =>
{
builder.UseSqlServer(config.GetConnectionString("myConn"));
}, ServiceLifetime.Transient, ServiceLifetime.Transient);
services.AddSingleton<ILoadTestCleanUpServiceRepository, LoadTestCleanUpServiceRepository>();
...
Now, the LoadTestCleanUpServiceRepository depends on the MyDbContext:
public class LoadTestCleanUpServiceRepository : ILoadTestCleanUpServiceRepository
{
private readonly MyDbContext _dbContext;
public LoadTestCleanUpServiceRepository(MyDbContext dbContext)
{
_dbContext = dbContext;
}
...
..and the DB Context is this:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> ctxOptions) : base(ctxOptions)
{
}
}
When I run the application, I get this error:
InvalidOperationException: Unable to resolve service for type
'MyCode.Infrastructure.Common.MyDbContext' while attempting to
activate
'MyCode.Infrastructure.LoadTestCleanUpService.LoadTestCleanUpServiceRepository'.
I have tried changing the ServiceLifetime options and adding this extra code:
services.AddTransient<MyDbContext>(sp => new MyDbContext(config));
...but nothing seems to help and I cannot understand why this doesn't work. It does try to construct the repository, but why can't it construct the DB Context too? It doesn't even reach the point where I call UseSqlServer()!
Any ideas?
UPDATE 1:
Hmm... I now see this. Most likely it is related:
UPDATE 2:
I have now :
Replaced EF 6 with Microsoft.EntityFrameworkCore.SqlServer
Upgraded to netcoreapp2.2 target framework to solve some conflicting assembly versions.
Made the repository scoped.
But I still get the same error.
I see you have registered LoadTestCleanUpServiceRepository as Singleton while MyDbContext as Transient and then you are trying to resolve MyDbContext from LoadTestCleanUpServiceRepository. That's the problem. According to ASP.NET Core Service lifetimes documentation:
It's dangerous to resolve a scoped service/transient service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
Solution is: register LoadTestCleanUpServiceRepository and MyDbContext as follows:
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("YourConnectionStringName")));
services.AddScoped<ILoadTestCleanUpServiceRepository, LoadTestCleanUpServiceRepository>();
Now problem should go away.

Web API using Core and .Net framework 4.5.2. CAnnot set connection string

I have an application that includes a Web API Core startup project.
I found that I cannot scaffold an existing database in .Net Core for a View.
So I decided for my Web API Core application that I will have to use the .Net framework and get the data in a project using EF6.
However in the startup project (Web API Core) I need to set the connection string. For this I need EF6, and it would seem that I cannot do this even if I add EF6 in the project (maybe this can't be done in a Core project?).
So this code fails where I attempt to add the context;
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddMvcOptions(o => o.OutputFormatters.Add(
new XmlDataContractSerializerOutputFormatter()));
var connectionString = Startup.Configuration["connectionStrings:propertiesConnectionString"];
services.AddDbContext<SurveyEntities>(o => o.UseSqlServer(connectionString));
services.AddScoped<IPropertiesRepo, PropertiesRepo>();
}
If I download EF6, I get this error;
Error CS0311 The type 'Properties.EF6.MSurveyV2Entities' cannot be
used as type parameter 'TContext' in the generic type or method
'EntityFrameworkServiceCollectionExtensions.AddDbContext(IServiceCollection,
Action, ServiceLifetime)'. There is no
implicit reference conversion from 'Properties.EF6.MSurveyV2Entities'
to 'Microsoft.EntityFrameworkCore.DbContext'.
So how do I fix this?
I found the answer in this link here.
https://learn.microsoft.com/en-us/aspnet/core/data/entity-framework-6
If the link breaks this is a summary;
In the EF6 project I put in a partial class for the db context where a constructor takes the connection string as a parameter
public partial class SurveyEntities
{
public SurveyEntities(string connString) : base(connString)
{
}
}
In the Web.API core project I have in the startup;
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore();
var connectionString = Configuration.GetConnectionString("SurveyEntities");
services.AddScoped<SurveyEntities>(_ => new SurveyEntities(connectionString));
services.AddScoped<IPropertiesRepo, PropertiesRepo>();
}
and in the controller I have
public class ContractController : Controller
{
private readonly IPropertiesRepo PropertiesRepo;
private readonly SurveyEntities db;
public ContractController(SurveyEntities _db,IPropertiesRepo repo)
{
this.db = _db;
this.PropertiesRepo = repo;
}
[HttpGet("getall")]
public IActionResult GetContracts()
{
var contracts = this.PropertiesRepo.GetAllLiveContracts().ToList();
return Ok(contracts);
}
}
The call to the api now works.
You need to use the connection string stored in appsetings.json. This is the way you have the most flexiblity configuring ASP.NET Core.
i.e.
{
"ConnectionStrings": {
"MyDatabase": "Server (localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;"
},
}
After that in configure services
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDatabase")));
}
You have more info and examples here https://learn.microsoft.com/en-us/ef/core/miscellaneous/connection-strings

Adding loading configuration into inject dependencies in asp.net core

In my Asp.Net Core API, I have some repository classes I'm injecting by using the services.AddTransient method in my Startup.cs
services.AddTransient<IRepository, Repository>();
I'm also loading a section of my appsettings.json into a custom object in the Startup.cs
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
My repository class needs to load the db connection string from the appsettings.json. Is there a way to inject these settings straight to the repository class? Or is it up to the constructor of the controller to pass the connection string to the repository class?
Yes, you can inject this directly to your repository.
public MyRepository(IOptions<MyConfig> config)
{
var connString = config.Value.DbConnString;
}
where MyConfig is a POCO object of your configuration and is getting added/registered in startup:
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<MyConfig>(Configuration);
}
The MyConfig can look something like this:
public class MyConfig
{
public string DbConnString { get; set; }
}
And the appsettings.json like this:
{
"ApplicationName": "MyApp",
"Version": "1.0.0",
"DbConnString ": "my-conn-string"
}

Categories