Passing in a new connection string for a test database - c#

I have a asp.net project with a DAL (most code is from someone else but I'm doing maintenance and clean up).
My web application project has a dataprovider class like so:
public class DataProvider : IDataProvider
{
private string DefaultConnectionString
{
get
{
return ConfigurationManager.ConnectionStrings[ConfigurationManager.AppSettings["myConnection"]].ConnectionString;
}
}
public T ExecuteMyQuery<T>(parameters)
{
//executes query on db
}
This web application project as has a data layer that interacts between the web pages and the provider like so:
public class MyData : IMyData
{
private readonly IDataProvider dataProvider;
public MyData(IDataProvider dataProvider)
{
this.dataProvider = dataProvider;
}
public string GetTitle(string myVar)
{
//builds up stuff to send
return this.dataProvider.ExecuteMyQuery(...);
}
}
The connection string for this project is in web.config.
Now I have my tests. For simplicity and time constraints I'm using just a test database and will create the actual objects and test against the test database (in theory we would use more mocks, stubs and all sorts of stuff but no time.)
So I have a test project which references my web application project above.
public class MyTests
{
private MyData myData;
private DataProvider dataProvider;
[SetUp]
protected void SetUp()
{
dataProvider = new DataProvider();
mysData = new MyData(dataProvider);
}
[Test]
public void CheckTitles()
{
string title = myData.GetTitle("AVariable");
Assert.AreEqual(title, "A typical title");
}
But this keeps give me a null pointer execption when I run it in NUnit. How do I get my provider to use a new connection string to point to my test database? I tried added an app.config file but it's not working.

It seems like your app.config (which is copy of web.config) is not loaded as configuration file by default when tests are being run.
I'd prefere to solve that in the following way:
Create IConfiguration interface which encapsulates details of getting connectionString
public interface IConfiguration {
string MyConnectionString { get; }
}
Create implementation of IConfiguration which reads web.config to use it in real code)
public class Configuration : IConfiguration
{
public string MyConnectionString
{
get { return ConfigurationManager.ConnectionStrings[ConfigurationManager.AppSettings["myConnection"]].ConnectionString; }
}
}
Introduce IConfiguration dependency into DataProvider class
public class DataProvider : IDataProvider
{
private IConfiguration configuration;
public DataProvider(IConfiguration configuration)
{
this.configuration = configuration;
}
private string DefaultConnectionString
{
get
{
return configuration.MyConnectionString;
}
}
...
}
Simple refactoring extracts knowledge about reading web.config from your DataProvider :)
In your test use IConfiguration stub which returns any connection string what you need for testing. (you can create stub by using mocking framework like moq/rhino-mock or by implementation of IConfiguration which does not read app.config)
public class MyTests
{
private MyData myData;
private DataProvider dataProvider;
private IConfiguration configuration;
[SetUp]
protected void SetUp()
{
// e.g. here stub is created via Moq
var configurationMock = new Mock<IConfiguration>();
configurationMock.SetupGet(x => x.MyConnectionString).Returns("test connection string");
configuration = configurationMock.Object;
dataProvider = new DataProvider(configuration);
mysData = new MyData(dataProvider);
}
...
}
If reading web.config is not goal of your tests then that approach is what you need.
As a bonus knowledge about configuration reading goes to separated class :)

Related

Is there a way to modify an IOptionsSnapshot<T> programmatically instead of a change in the source file?

I have a transient class registered in ASP.NET Core's DI.
An IOptions<T> is injected into its constructor. For every request and when needed, during runtime and based on a condition, I want to have another instance of IOptions injected.
Since IOptionsSnapshot<T> gets updated for every request when the source file gets updated, is there a way to mimic this behavior but instead of a change in the file, I want to programmatically make a change in IOptions, and before constructor injection, during runtime when a request comes in?
And use IOptionsSnapshot<T> instead of IOptions<T> for that.
Update:
Condition example to be run before injection happens somewhere in the app like maybe a controller or action custom attribute? In the attribute check a value and so:
if (some condition)
Options.cnnectionstring = "string1";
else
Options.cnnectionstring = "string2";
Injected into a class like this:
public class Books
{
private readonly string connectionString;
public Books(IOptions<DBOptions> options)
{
this.connectionString = options.Value.connectionString;
}
public void SomeMethod()
{
.... //uses connectionString
}
}
Registered like this:
services.Configure<DBOptions>(options =>
{
options.connectionString = "some connection string";
});
IOption (like IConfiguration) is registered as singleton, but the request is scoped. Then it isn't possible to use request's information to modify the configuration.
You can use a intermediate scoped service, that retrieve the request's information and generate the desired connection string, like :
public class BooksConnectionString
{
public IConfiguration _configuration;
public IHttpContextAccessor _httpContextAccessor;
public BooksConnectionString(IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
{
_configuration = configuration;
_httpContextAccessor = httpContextAccessor;
}
public string ConnectionString
{
get
{
var library = _httpContextAccessor.HttpContext.Request.Query["library"].First();
return _configuration.GetConnectionString(library);
}
}
}
Register the service as scoped :
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHttpContextAccessor();
services.AddScoped<BooksConnectionString>();
}
...
}
Then you can inject like :
public class Books
{
private readonly string connectionString;
public Books(BooksConnectionString options)
{
this.connectionString = BooksConnectionString.ConnectionString;
}
}
It's suppose you can modify the class where the connection string is injected. If you can't modify this class, then you can register IOption as scoped :
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHttpContextAccessor();
services.AddScoped<IOptions<DBOptions>>(p =>
{
var configuration = p.GetService<IConfiguration>();
var httpContextAccessor = p.GetService<IHttpContextAccessor>();
var library = httpContextAccessor.HttpContext.Request.Query["library"].First();
var dbOptions = configuration.GetSection("Databases").GetSection(library).Get<DBOptions>();
return Options.Create(dbOptions);
});
}
...
}
Warning, IOption are expected as singleton. Register IOption as scoped would break this expectation. To be used as a last resort.

Dependency injection between two ASP.NET Core projects

I'm currently developing a web application with ASP.NET Core and handling the database with Entity Framework Core. I have two projects in my VS Solution; WebApp (the main application) and DatabaseHandler (the EF Core handler). I have installed Entity Framework Core with the Pomelo package, since I'm using a MySQL database.
I've been following the Microsoft documentation to setup EF Core, connection strings and all that, and it works fine. I'm able to make migrations, make updates and do stuff with the database. I'm however not sure if I'm doing it correctly, since the latest EF Core tutorials use dependency injection and I'm not familiar with it.
Right now I'm passing the DbContext object as an argument from WebApp to DatabaseHandler, since I want all database-related stuff to only exist in DatabaseHandler. This works, but is it possible to call functions from another project and also share the DbContext object without passing it as an argument? I'm probably not explaining it well, I hope my code explains it better.
WebApp/Startup.cs:
This is where I load the connection string from appsettings.json.
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<DataContext>(
options => options.UseMySql(Configuration.GetConnectionString("DefaultConnection")
));
services.AddRouting(options => options.LowercaseUrls = true);
services.AddControllersWithViews();
}
WebApp/HomeController.cs:
This is where I call the GetAllChallenges() function from the DatabaseHandler project, and I also pass the DataContext object as an argument. This is what I'm trying to avoid!
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly DataContext db;
public HomeController(ILogger<HomeController> logger, DataContext _db)
{
_logger = logger;
db = _db;
}
public IActionResult Challenges()
{
List<Challenge> ChallengesList = DatabaseHandler.HandleChallenges.GetAllChallenges(db);
return View(ChallengesList);
}
}
DatabaseHandler/DataContext.cs:
This is where I initialize the entity classes and so on.
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { }
// Tables
public DbSet<User> Users { get; set; }
public DbSet<Challenge> Challenges { get; set; }
// Data seeding
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Seed();
}
}
DatabaseHandler/HandleChallenges.cs:
This is where I have all my database functions. The results are returned back to the controller within the WebApp project.
public class HandleChallenges
{
public static List<Challenge> GetAllChallenges(DataContext db)
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
I have looked into dependency injection, but I'm not sure how I can use this between two projects. Is there a less complicated way of achieving this, perhaps without using DI at all? I'm satisfied as long as I don't need to pass the DataContext object as an argument every time I need to call a function from DatabaseHandler.
Can someone help me understand? Thanks a lot in advance!
You could use Options pattern, which I have already used many times. Its working very well despite of database you use. Thanks to dependency injection you are able to access if from multiple projects. Reading documentation about Option pattern (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1) is useful but I will also provide you with my own example :
First you create model to store you connection string, dbName etc. Remember to add it in a library outside your main project(eg. Web Api) :
public class NameOfYourProject_ApiDbSettings : IIMTTApiDbSettings
{
public NameOfYourProject_ApiDbSettings()
{
}
public string CollectionName { get; set; }
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}
public interface I_NameOfYourProject_ApiDbSettings
{
string CollectionName { get; set; }
string ConnectionString { get; set; }
string DatabaseName { get; set; }
}
Secondly you make it available for all you projects :
services.Configure<NameOfYourProjectApiDbSettings>(options =>
{
options.ConnectionString
= Configuration.GetSection("NameOfYourProjectDbSettings:ConnectionString").Value;
options.DatabaseName
= Configuration.GetSection("NameOfYourProjectDbSettings:DatabaseName").Value;
});
Then you can use it in multiple projects. (Rememebr to add referance to you model -> point 1. I keep the model always with repository) I will give you my example where I use MongoDb :
private readonly IMongoDatabase _database = null;
public SomeObjectContext(IOptions<IMyProjectDbSettings> settings)
{
var client = new MongoClient(settings.Value.ConnectionString);
if (client != null)
_database = client.GetDatabase(settings.Value.DatabaseName);
}
public IMongoCollection<MyModel> MyModels
{
get
{
return _database.GetCollection<MyModel>("MyModels");
}
}
You need to extract an interface from the class (note the method is no longer static) and add a constructor for the context:
public interface IHandleChallenges
{
List<Challenge> GetAllChallenges();
}
public class HandleChallenges : IHandleChallenges
{
public HandleChallenges(DataContext context)
{
db = context;
}
private DataContext db;
public List<Challenge> GetAllChallenges()
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
Then register it as a service:
services.AddScoped<IHandleChallenges, HandleChallenges>();
Your controller now receives this class in it's constructor instead of the context:
private IHandleChallenges _challengeHandler;
public HomeController(ILogger<HomeController> logger, IHandleChallenges challengeHandler)
{
_logger = logger;
_challengeHandler = challengeHandler;
}
And calls it from the action:
public IActionResult Challenges()
{
List<Challenge> ChallengesList = _challengeHandler.GetAllChallenges();
return View(ChallengesList);
}

Injection in a referenced class library?

I have a ASP.NET Core 2.1 website that references a class library(DAL). To access the connectionstring from the appsettings.json(ASP.NET project) I need inject the configuration somehow. I have created a class in the class Library that looks like this :
public class Helper
{
IConfiguration Configuration;
public Helper(IConfiguration configuration)
{
Configuration = configuration;
}
public string GetConnectionString(string name)
{
return Configuration.GetConnectionString("DefaultConnection");
}
}
The injection pattern do however not pick this up so it demands a IConfigration to create the class.
How do I access the appsettings.json from the class library?
Your class library should not know or care how you're handling configuration in your app. All your Helper class needs is a connection string, so that is what you should inject into it. How that string is provided is an implementation detail that's part of your application domain.
public class Helper
{
public Helper(string connectionString)
{
// do something with connectionString
}
}
Then, in your app:
services.AddScoped(p =>
new Helper(Configuration.GetConnectionString("DefaultConnection")));

ASP.NET Core DI in a class library?

I have a ASP.NET Core 2.1 project that references a "Data Access Layer" project of typ .NET Core Class Library.
The Data Access Layger needs connection string from the appsettings.json in the ASP.NET Core project.
I have created a simple container like this :
public class DatabaseConnectionString : IDatabaseConnectionString
{
private readonly string _connectionString;
public DatabaseConnectionString(string connectionString)
{
_connectionString = connectionString;
}
public string ConnectionString {
get { return _connectionString; }
set { }
}
}
In the ASP.NET Core Startup.cs > ConfigureService I have this :
services.AddScoped<IDatabaseConnectionString>(p => new DatabaseConnectionString(Configuration.GetConnectionString("DefaultConnection")));
I know that I can add the IDatabaseConnectionString to a constructor of a controller in ASP.NET to get the container. But How do I get it while in the class library? I dont want to pass it all the way down from the controller and just adding the IDatabaseConnectionString to the constructor of a class in the class library do not work.
I probably need a service where I can ask to create a object of a class and let the service fill in the constructor interfaces with the correct objects?
For example filling in the IDatabasConnectionString in this class :
public class UserFactory : FactoryBase
{
private readonly IDatabaseConnectionString _iDatabaseConnectionString;
public UserFactory(IDatabaseConnectionString connectionString)
{
_iDatabaseConnectionString = connectionString;
}
}
I know that I can add the IDatabaseConnectionString to a constructor of a controller in ASP.NET to get the container.
No, that's not needed and it would be wrong.
just adding the IDatabaseConnectionString to the constructor of a class in the class library do not work.
It doesn't work because you need to create the service that will use the connection string and add it to the services container.
For example:
public class Repository: IRepository
{
public Repository(IDatabaseConnectionString databaseConnectionString)
{
_databaseConnectionString = databaseConnectionString;
}
}
public class ServiceThatRequiresDatabase : IServiceThatRequiresDatabase
{
public ServiceThatRequiresDatabase(IRepository repository)
{
_repository = repository;
}
}
// ...
services.AddScoped<IRepository, Repository>();
services.AddScoped<IServiceThatRequiresDatabase, ServiceThatRequiresDatabase>();
public class HomeController : Controller
{
public HomeController(IServiceThatRequiresDatabase service)
{
_service = service;
}
}
By the way, as #YeldarKurmangaliyev said, your DatabaseConnectionString should be like this if you want to make it read-only:
public class DatabaseConnectionString : IDatabaseConnectionString
{
public string ConnectionString { get; }
public DatabaseConnectionString(string connectionString)
{
ConnectionString = connectionString;
}
}
There is no difference between controller and class from a class library. You need to
Define a class in a class library and inject IDatabaseConnectionString into it. Your UserFactory is the right way.
register the UserFactory for DI
serviceCollection.AddScoped<IUserFactory, UserFactory>();
Resolve the UserFactory by the DI. For example, use the UserFactory as the constructor parameter in some controller. Everything is connected by DI automatically.
public MyController(IUserFactory userFactory)
{
_userFactory = myUserFactory;
}
Here is the good explanation for understanding Composition root.

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; }
}

Categories

Resources