How to Pass IConfiguration from API controller to Data Access Layer - asp.net-core

I am new to ASP.net core, and trying to convert my existing RESTful WepAPIs to ASP.Net Core API (2.2).
Following is my present application structure:
[WebAPI Methods] calls -> [Business Logic Layer (BLL)] calls -> [Data Access Layer (DAL)].
In order to open DB connection from DAL, I am calling static method of another class library (Infrstructure.Data.dll) and passing connection string name as Enum. This library gets the actual connection string from the Web.Config of the webAPI using the following
string connString = ConfigurationManager.ConnectionStrings[GetDatabaseHint(dbSource)].ConnectionString;
and returns IDbConnection which my DAL uses it to transact with DB.
Now, I am looking for similar options in ASP.net core Web API.
I have so far come up with the following:
In the Startup.cs file of API, added
services.AddSingleton(Configuration);
In the Infrstructure.Data.dll class library, added
private static IConfiguration _config;
public ConnectionFactory(IConfiguration config)
{
_config = config;
}
public static IDbConnection GetConnection(DBSource dbSource)
{
string connString = _config.GetConnectionString(GetDatabaseHint(dbSource));
//... Code to create connection using DB Providers
return conn;
}
IConfiguration is always null here in this Infrstructure.Data.dll.
Please let me know if in case this is an incorrect approach, and what should be the right way of achieving this.

Follow steps below to access IConfiguration in Infrstructure.Data
Define ConnectionFactory
public class ConnectionFactory
{
private static IConfiguration _config;
public ConnectionFactory(IConfiguration config)
{
_config = config;
}
public string GetConnection()
{
return _config.GetConnectionString("any connection string name");
}
//public static IDbConnection GetConnection(DBSource dbSource)
//{
// return new dbc
//}
}
Register ConnectionFactory in Startup.cs
services.AddScoped<ConnectionFactory>();
Useage
public class HomeController : Controller
{
private ApplicationDbContext _context;
private readonly ConnectionFactory connectionFactory;
public HomeController(ApplicationDbContext context
, ConnectionFactory connectionFactory)
{
_context = context;
this.connectionFactory = connectionFactory;
}
public IActionResult Index()
{
string result = connectionFactory.GetConnection();
return View();
}
}
Note IConfiguration is registed default by WebHost.CreateDefaultBuilder(args)
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}

Related

ASP.NET Core - Create a singleton class to supply connection string across the application

I'm creating a ASP.NET Core Web API using ADO.NET (without Entity Framework). I need a singleton class to supply connection string to all the controllers. I have done the following.
Defined a class DBUtils to have just one public property DBConnectionString.
Try to register the class as a singleton in startup.cs.
Use the class through DI in each controller to access the connection string.
public class DBUtils
{
public string DBConnectionString { get; set; }
public DBUtils(string connectionString)
{
this.DBConnectionString = connectionString;
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<DBUtils>();
services.AddControllers();
}
}
public class CommonController : ControllerBase
{
private string conStr;
public CommonController(DBUtils utils)
{
conStr = utils.DBConnectionString;
}
public IActionResult GetData() {
SqlConnection connection = new SqlConnection(conStr);
//Get dat from the database
return null;
}
}
Now the problem is I'm not able to pass the connection string to the DBUtils constructor. I read from other posts that we should not use parameters to Singleton classes. But my class will only have one parameter and it will never change during execution. It gets the connection string from config file.
please help how to I pass connection string to my controllers.
I don't want to use IConfiguration as DI in the controller class directly.
UPDATE: I realised that Singleton is not the approach for my requirement and as #Ceemah Four suggested we should use Options Pattern.
Thanks
This scenario has already been catered for in dotnet core.
You do not need to create the DBUtils class. Neither do you need to set up the Singleton DI etc.
Assuming this is your appsettings.json
"ConnectionStrings": {
"SqlDatabase": "connection string here"
}
There are two potential approaches:
Inject IConfiguration in Controller constructor - you can simply access the connection string value from the injected Configuration.
public class CommonController : ControllerBase
{
private readonly IConfiguration _config;
private string conStr;
public CommonController(IConfiguration config)
{
_config = config;
}
public IActionResult GetData()
{
SqlConnection connection = new SqlConnection(_config.GetConnectionString("SqlDatabase"));
//Get data from the database
return null;
}
}
Create a Settings class, bind the settings class in Startup and inject the Settings class in the controller constructor. This uses the IOPtions pattern * and is a cleaner and recommended approach*
public class ConnectionSettings
{
public string SqlDatabase { get; set; }
}
In your startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<ConnectionSettings>(Configuration.GetSection("ConnectionStrings"));
services.AddControllers();
}
}
Then in your controller:
public class CommonController : ControllerBase
{
private readonly IOptions<ConnectionSettings> _connectionSettings;
public CommonController(IOptions<ConnectionSettings> connectionSettings)
{
_connectionSettings = connectionSettings;
}
public IActionResult GetData()
{
SqlConnection connection = new SqlConnection(_connectionSettings.Value.SqlDatabase));
//Get data from the database
return null;
}
}

static types cannot be used as type arguments

I am porting from traditional .net mvc application to .netcore.
Original application flow:
OSMSelectController -> OSMDB_Loader.cs -> StatusController
where the connectionstring is read and DAL is initialized.
This connectionstring is coming from static class but when I debug the value is null in here:
WPSGlobalSettings.ToolboxConnString
I have a static class for reading connectionstring from appsettings.
WPSGlobalSettings
public static class WPSGlobalSettings
{
public static NpgsqlConnectionStringBuilder ToolboxConnString = build_conn_str(ToolboxDatabaseName);
private static NpgsqlConnectionStringBuilder build_conn_str(string dbname)
{
string dbSetting = ConfigurationHelper.config.GetSection("ConnectionStrings")["DefaultConnection"];
...
}
}
Controller
public class StatusController : Microsoft.AspNetCore.Mvc.ControllerBase
{
protected StatusDAL status_dal = new StatusDAL(WPSGlobalSettings.ToolboxConnString);
}
Here it gives type exception, wpsglobalsettings was not initialized and toolboxconnstring is null.
I have tried adding it as singleton to Startup but then i get
static types cannot be used as type arguments
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
services.AddControllersWithViews();
services.AddSingleton<WPSGlobalSettings>();
ConfigurationHelper.Initialize(Configuration);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
WPSGlobalSettings.Configure(env);
...
}
Edit:
I have removed following from Startup.cs
services.AddSingleton<WPSGlobalSettings>();
Also, introduced DI as follows
protected StatusDAL status_dal;//= new StatusDAL(WPSGlobalSettings.ToolboxConnString);
public StatusController(IConfiguration config)
{
status_dal = new StatusDAL(config.GetConnectionString("toolboxConnectionStrWPS"));
}
Now another problem is older code calls controller constructor from another class as follows:
OSMDB_Loader.cs
public StatusAck LoadOSMSections(OsmLoadRequest request)
{
StatusAck statusack = new StatusController().PostStatusRecord();
}
Therefore I also added simple constructor in StatusController:
public StatusController()
{
}
but now ofcourse status_dal is always null
Something is not quiet right!
I got some help here and here
Also, the fact that static creates problem with multi-threading..from here
Here is my solution:
Original application flow was
OSMSelectController -> OSMDB_Loader.cs -> StatusController
where the connectionstring was read and DAL was initialized.
Now:
I injected IConfiguration in OSMSelectController and passed it to OSMDB_Loader.cs
private IConfiguration _config;
public OsmSelectController(IConfiguration config)
{
_config = config;
}
public IActionResult AddRoadsOsm([FromBody]OsmLoadRequest request)
{
...
StatusAck statusack = new OSM_DBLoader(user_id).LoadOSMSections(request, _config);
}
OSMDB_Loader.cs
public StatusAck LoadOSMSections(OsmLoadRequest request, IConfiguration conf)
{
StatusAck statusack = new StatusController(conf).PostStatusRecord(); // Issue here is that you need to get the uri
...
}
This way StatusController's correct constructor is hit from where I am able to read the configuration value for connection string
StatusController
protected StatusDAL status_dal;
public StatusController()
{
status_dal = new StatusDAL(WPSGlobalSettings.ToolboxConnString);
}
public StatusController(IConfiguration config)
{
status_dal = new StatusDAL(config.GetConnectionString("toolboxConnectionStrWPS"));
}
I would still appreciate if someone can detail how GlobalSettings Static files pose a problem and to cater which we have DependencyInjection and Singleton pattern in .net core.

How to access Controller class variable value in data access layer?

Code in appsettings.json:
{
"Logging":{
"LogLevel":{
"Default":"Warning"
}
},
"AllowedHosts":"*",
"ConnectionStrings":{
"DefaultConnection":"Server=localhost;Port=5432;Database=CrudDataBase;User Id=xyy;Password=xyz123###;"
}
}
Code in HomeController:
[Route("api/[controller]/[action]")]
[ApiController]
public class EcoSystemHomeController : ControllerBase
{
EcoBusinessHome ebl= new EcoBusinessHome();
public string ConnectionString;
private readonly IConfiguration configuration;
public EcoSystemHomeController(IConfiguration config)
{
this.configuration = config;
}
[HttpGet]
public object GetServiceStatus()
{
try
{
ConnectionString = configuration.GetConnectionString("DefaultConnection");
JObject StatusObj = new JObject();
StatusObj.Add(ConnectionString);
return StatusObj;
}
catch
{
return null;
}
}
}
My app follows the three-layer architecture. I have a variable named ConnectionString inside EcoSystemHomeController. It holds the connection string value. I want to use this ConnectionString variable in the data access layer. How can I achieve it?
Reading AppSettings from AppSettings.json using IConfiguration interface
In the below example, the IConfiguration is injected in the Controller and assigned to the private property Configuration.
Then inside the Controller, the AppSettings are read from the AppSettings.json file using the GetSection function.
Test Code:
private IConfiguration Configuration;
public TestController(IConfiguration _configuration)
{
Configuration = _configuration;
}
public IActionResult Index()
{
//ConnectionStrings
string appName = this.Configuration.GetSection("ConnectionStrings")["DefaultConnection"];
return View();
}
Result:

Create database context from cookie and base path in Entity Framework Core

Postgres database has multiple schemes like company1, company2, ... companyN
Browser sends cookie containing scheme name . Data access operations should occur in this scheme. Web application user can select different scheme. In this case different cookie value is set.
Npgsql EF Core Data provider is used.
ASP NET MVC 5 Core application registers factory in StartUp.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddScoped<IEevaContextFactory, EevaContextFactory>();
....
Home controller tries to use it:
public class HomeController : EevaController
{
public ActionResult Index()
{
var sm = new SchemeManager();
sm.PerformInsert();
....
This throws exception since factory member is null. How to fix this ?
public interface IEevaContextFactory
{
EevaContext Create();
}
public class EevaContextFactory : IEevaContextFactory
{
private IHttpContextAccessor httpContextAccessor;
private IConfiguration configuration;
public EevaContextFactory(IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
{
this.httpContextAccessor = httpContextAccessor;
this.configuration = configuration;
}
public EevaContext Create()
{
var builder = new DbContextOptionsBuilder<EevaContext>();
var pathbase = httpContextAccessor.HttpContext.Request.PathBase.Value;
var scheme = httpContextAccessor.HttpContext.Request.Cookies["Scheme"];
var csb = new NpgsqlConnectionStringBuilder()
{
Host = pathbase,
SearchPath = scheme
};
builder.UseNpgsql(csb.ConnectionString);
return new EevaContext(builder.Options);
}
}
Scheme data acess methods:
public class SchemeManager
{
readonly IEevaContextFactory factory;
public SchemeManager(IEevaContextFactory factory)
{
this.factory = factory;
}
public SchemeManager()
{
}
public void PerformInsert()
{
using (var context = factory.Create())
{
var commandText = "INSERT into maksetin(maksetin) VALUES (CategoryName)";
context.Database.ExecuteSqlRaw(commandText);
}
}
}
var sm = new SchemeManager()
... will call the no-parameter constructor on SchemeManager so the IEevaContextFactory is not injected. You should inject your factory into your controller and pass it into your SchemeManager.
Remove your no-parameter constructor. It's not needed.
public class HomeController : EevaController
{
private IEevaContextFactor eevaFactory;
public HomeController(IEevaContextFactory factory)
{
eevaFactory = factory;
}
public ActionResult Index()
{
var sm = new SchemeManager(eevaFactory);
sm.PerformInsert();
....
}
}
Your other option is to put the SchemeManager in the DI container and then the DI container will auto-resolve IEevaContextFactory on the constructor and then just inject SchemeManager into your controller.
Either way, remove that no-parameter constructor.

.Net Core passing connection string to DBContext class

Just started using .Net Core and facing passing connection string information to Context console project.
I have 4 projects, created using .Net Core.
MVC
Service Layer
Domain Layer
Data Layer
In MVC project, I have Startup.cs file where i am reading appsettings.json file
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
// Add appsettings
services.Configure<AppSettingsConfig>(Configuration.GetSection("AppSettings"));
}
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
if (env.IsDevelopment())
{
// This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
builder.AddApplicationInsightsSettings(developerMode: true);
}
Configuration = builder.Build();
}
In my 4th project (Data Layer), which Console Project and having following DBContext class. This project doesn't have Startup.cs as i MVC project having. Not created by default by VS 2015.
public class MyDWContext : DbContext
{
public MyDWContext() : base ()
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Data Source=localhost;Initial Catalog=MyDW; Persist Security Info = False; User ID = TempUser; Password = Temp123");
}
public DbSet<User> Users { get; set; }
public DbSet<Class> Classs { get; set; }
}
I have been to other post as well but i believe its created using older version or RC version. So some time i cannot find correct object or .Net classes.
As i have connection string is in MVC project, how can i use connection string during my MVC call to Data layer.
I have Web.API (Core) project as well and that having own connection string (different user configuration in connection string which having only read access). How can i use Web2.API connection string when i am making call from Web2.API project.
Instead of passing connection string to DbContext, configuring DbContext in Startup.cs(if possible) is better way. See official docs to understand how to configure DbContext and use it via Dependency Injection.
EDIT : Below code is not good way
However, if you want to pass connection string to DbContext you can use options pattern.
Here is an example how to pass connection string with options pattern:
First you need an options class which accessible from Data Layer and MVC layer
public class ConnectionStringOption
{
public string ConStr { get ; set; }
}
Then set option value
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<ConnectionStringOption>(options=>
{
// set connection string from configuration
options.ConStr = Configuration.GetConnectionString("Default");
});
}
appsetting.json
{
"ConnectionStrings": {
"Default": "<your connection string>"
}
}
Finally DbContext
private readonly IOptions<ConnectionStringOption> _conStrOptions;
protected YourDbContext()
{
}
public YourDbContext(IOptions<ConnectionStringOption> conStrOptions, DbContextOptions options)
: base(options)
{
_conStrOptions= conStrOptions;
}
Edit for another way
Using Static Service Locator may be a solution:
Create a DependencyResolver in Data Layer
public static class DependencyResolver
{
private static IServiceProvider _provider;
public static IServiceProvider ServiceProvider
{
get
{
return _provider;
}
set
{
if(_provider == null)
{
_provider = value;
}
}
}
}
In ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
// other stuff
services.AddOptions();
services.Configure<ConnectionStringOption>(options=>
{
// set connection string from configuration
options.ConStr = Configuration.GetConnectionString("Default");
});
DependencyResolver.ServiceProvider = services.BuildServiceProvider();
}
And finally get option:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var conStr = DependencyResolver.ServiceLocator.GetService<IOptions<ConnectionStringOption>>().Value.ConStr;
optionsBuilder.UseSqlServer();
}
Final Edit for previous stupid way
public static class ConnectionStringGetter
{
public static string ConStr{get;set;}
}
public Startup(IHostingEnvironment env)
{
//...
Configuration = builder.Build();
ConnectionStringGetter.ConStr = Configuration.GetConnectionString("Default");
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(ConnectionStringGetter.ConStr);
}