I created a ASP.NET class library in my ASP.NET5 application. I wanted to add Startup.cs file in order to load some configuration from .json (ConnectionString) file to use it in project and add it to DI. Then I wanted to inject AppSettings into my DbContext which is in the same project. But this is not working, it seems code in Startup class is never executed.
Injecting AppSettings into DbContext throws Exception
InvalidOperationException: Unable to resolve service for type 'Beo.Dal.Properties.AppSettings' while attempting to activate 'Beo.Dal.DataContexts.ApplicationDbContext'.
DbContext
private static bool IsCreated;
private AppSettings AppSettings;
public ApplicationDbContext(AppSettings settings)
{
AppSettings = settings;
if (!IsCreated)
{
Database.AsRelational().ApplyMigrations();
IsCreated = true;
}
}
Did I miss somthing or I got it all wrong? I already got one Startup.cs in MVC project and it works fine. Can I use it in class library at all? If not how should I load this .json?
This is what I was actually doing inside:
public class Startup
{
public AppSettings Settings { get; set; }
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
Settings = new AppSettings();
}
public void Configure(IApplicationBuilder app)
{
string connectionString;
if (Configuration.TryGet("Data:DefaultConnection:ConnectionString", out connectionString))
{
Settings.ConnectionString = connectionString;
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddInstance(Settings);
}
}
You can't have a second Startup.cs file because a class library is not a project that can startup. What you should do is take advantage of the dependency injection and add your DbContext to the services. Now when you need the DbContext for your repositories it'll be ready to go.
There is an example in the DI docs for Core http://docs.asp.net/en/latest/fundamentals/dependency-injection.html#using-framework-provided-services
Related
I have different classes which inherit of a base class. The base class implements the interface IHealthCheck. Each class has a constructor which need a logger and parameters according to the class.
For example :
public ConnectionHealthCheck(ILogger logger, string address)
: base(logger)
{
Address = address;
}
I have a appSettings.json which allows me to configure several diagnostics to do in my Health Check service.
I get the list of diagnostics in my App.xaml.cs and i'm trying to add them in the HealthCheck list.
The problem is that I cannot do a dependency injection with parameters next to it and I don't know what is the best solution to do it...
Here is some parts of my code.
The OnStartup method :
protected override void OnStartup(StartupEventArgs e)
{
var a = Assembly.GetExecutingAssembly();
using var stream = a.GetManifestResourceStream("appsettings.json");
Configuration = new ConfigurationBuilder()
.AddJsonStream(stream)
.Build();
var host = new HostBuilder()
.ConfigureHostConfiguration(c => c.AddConfiguration(Configuration))
.ConfigureServices(ConfigureServices)
.ConfigureLogging(ConfigureLogging)
.Build();
[...] }
The configureService Method :
private void ConfigureServices(IServiceCollection serviceCollection)
{
// create and add the healthCheck for each diag in the appSettings file
List<DiagnosticConfigItem> diagnostics = Configuration.GetSection("AppSettings:Diagnostics").Get<List<DiagnosticConfigItem>>();
diagnostics.ForEach(x => CreateHealthCheck(serviceCollection, x));
[...] }
And the method CreateHealthCheck where is the problem :
private void CreateHealthCheck(IServiceCollection serviceCollection, DiagnosticConfigItem configItem)
{
EnumDiagType type;
try
{
type = (EnumDiagType)Enum.Parse(typeof(EnumDiagType), configItem.Type, true);
}
catch (Exception)
{
throw new Exception("Diagnostic type not supported");
}
switch (type)
{
case EnumDiagType.Connection:
serviceCollection.AddHealthChecks().AddCheck(nameof(ConnectionHealthCheck), new ConnectionHealthCheck(???, configItem.Value));
break;
case EnumDiagType.Other:
[...] }
As you can see, I cannot create the instance of the ConnectionHealthCheck class because I cannot reach the ILogger object...
So how can I do it ? I think about different solutions but I don't have the answer or the way to do it
Build the HealthCheck service not in the App.xaml.cs but after ? (In a view model for exemple where I have access to the serviceCollection and the logger)
Find a way to get the logger to use it in the CreateHealthCheck method ?
Do something like that but I don't know when I can pass the parameters
serviceCollection.AddHealthChecks().AddCheck<ConnectionHealthCheck>(nameof(ConnectionHealthCheck));
You can use HealthCheckRegistration to register your class (it should implement IHealthCheck), it has constructors accepting delegate Func<IServiceProvider,IHealthCheck> which allows you to use IServiceProvider to resolve required parameters to create an instance of your healthcheck class. Something like this:
public static class ConnectionHealthCheckBuilderExtensions
{
const string DefaultName = "example_health_check";
public static IHealthChecksBuilder AddConnectionHealthCheck(
this IHealthChecksBuilder builder,
string name,
DiagnosticConfigItem configItem,
HealthStatus? failureStatus = default,
IEnumerable<string> tags = default)
{
return builder.Add(new HealthCheckRegistration(
name ?? DefaultName,
sp => new ConnectionHealthCheck(sp.GetRequiredService<ISomeService>(), configItem.Value),
failureStatus,
tags));
}
}
See this part of docs for more details.
The .NET Core in-built DI can inject the components on the Constructor level.
So use the following way, which I use in my ASP.NET Core Projects.
public class Startup
{
public Startup(IWebHostEnvironment environment, IConfiguration configuration, ILoggerFactory loggerFactory)
{
Environment = environment;
Configuration = configuration;
LoggerFactory = loggerFactory;
}
public IConfiguration Configuration { get; }
public ILoggerFactory LoggerFactory { get; }
public IWebHostEnvironment Environment { get; }
private void ConfigureServices(IServiceCollection serviceCollection)
{
List<DiagnosticConfigItem> diagnostics = Configuration.GetSection("AppSettings:Diagnostics").Get<List<DiagnosticConfigItem>>();
diagnostics.ForEach(x => CreateHealthCheck(serviceCollection, x, LoggerFactory));
}
private void CreateHealthCheck(IServiceCollection serviceCollection, DiagnosticConfigItem configItem)
{
// Create a ILogger<T> based on your Type by
loggerFactory.CreateLogger<MessagingServices>())
}
}
This might be crude, but hope this helps.
I have the following situation:
startup.cs
services.AddSingleton<IConfigurationManager, ConfigurationManager>();
ConfigurationManager configurationManager = new ConfigurationManager();
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(configurationManager.DatabaseConnectionString));
So, in order to create my context I need the connection string which this configurationManager provides to me. However, I would still like to keep the ConfigurationManager as a service.
Is there a way to do this without explicitly instantiating the configurationManager or is it perhaps even fine to leave it like this?
It's possible to access the IServiceProvider while building the context:
services.AddDbContext<MyContext>((serviceProvider, options) =>
{
var configManager = serviceProvider.GetService<IConfigurationManager>();
options.UseSqlServer(configManager.DatabaseConnectionString);
});
However, here your best options might be to read the Iconfiguration injected in Startup.cs:
public class Startup
{
public IConfiguration Configuration { get; }
public IHostingEnvironment HostingEnvironment { get; }
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
HostingEnvironment = env;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("MyContext"));
});
}
}
You can use the service provider to get instances of services:
Build the services provider -
var provider = services.BuildServiceProvider();
Get specific service -
provider.GetService<T>();
Although a better option would be to use the IConfiguration .NET Core provides - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1
Using this method you can set IConfiguration to parse your config settings in the Startup method. From there you can then inject these settings into the required classes.
Also you can do sth similar to this.
I am not familiar with what you do with your configuration manager to provide a precise answer.
Basically you can do a pre-config inside your Program.cs.
Build your configuration here. As you can see i am passing IConfigurationBuilder.
Program.cs
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(AddDbConfiguration)
.UseStartup<Startup>();
private static void AddDbConfiguration(WebHostBuilderContext context, IConfigurationBuilder builder)
{
var configuration = builder.Build(); // you could instantiate you Configuration manager here and append to the configuration.
var connectionString = configuration.GetConnectionString("Database");
builder.AddDemoDbProvider(options => options.UseSqlServer(connectionString));
}
source: https://medium.com/#dneimke/custom-configuration-in-net-core-2-193ff6f02046
In my solution, I have a ASP.NET Core web project and a .NET Standard class library project. Class library project is the data access layer and I want to read the connection string from my appsettings.json (ASP.NET Core project) in my data access layer.
I have found few answers such as by Andrii Litvinov which looks like quite straight forward to implement but he also mentioned about implementing through Dependency Injection. I don't want to choose the easy shortcut way but looking for the dependency injection implementation?
I am not sure if having appsettings.json in my class library and then registering it through IConfigurationRoot is the better option (as explained here by JRB) but in my scenario, the connection string is in the appsettings.json file of the web project and I wan't constructor dependency injection implementation in my class library project to consume the connection string.
You can inject an instance of a class that implements IConfiguration
See Here
Let's assume in your .net core app, you have a configuration file that looks something like this:
{
"App": {
"Connection": {
"Value": "connectionstring"
}
}
}
In your data access layer (class library) you can take a dependency on IConfiguration
public class DataAccess : IDataAccess
{
private IConfiguration _config;
public DataAccess(IConfiguration config)
{
_config = config;
}
public void Method()
{
var connectionString = _config.GetValue<string>("App:Connection:Value"); //notice the structure of this string
//do whatever with connection string
}
}
Now, in your ASP.net Core web project, you need to 'wire up' your dependency.
In Startup.cs, I'm using this (from the default boilerplate template)
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.AddMvc();
services.AddSingleton<IConfiguration>(Configuration); //add Configuration to our services collection
services.AddTransient<IDataAccess, DataAccess>(); // register our IDataAccess class (from class library)
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
Now, when your code in your class library gets executed, the ctor gets handed the instance of IConfiguration you have set up in your web app
Note:
You can create strongly typed settings class if you'd prefer, see here for more information
I would suggest Options pattern. You can create the class with configuration data, e.g.:
public class ConnectionStringConfig
{
public string ConnectionString { get; set; }
}
Register it on Startup:
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<ConnectionStringConfig>(Configuration);
}
and inject in your data access layer
private readonly ConnectionStringConfig config;
public Repository(IOptions<ConnectionStringConfig> config)
{
this.config = config.Value;
}
It's pretty simple...use IOptions at the composition root like so in startup.cs or in a separate class library project:
services.AddScoped<IDbConnection, OracleConnection>();
services.AddScoped<IDbConnection, SqlConnection>();
services.Configure<DatabaseConnections>(configuration.GetSection("DatabaseConnections"));
services.AddScoped(resolver =>
{
var databaseConnections = resolver.GetService<IOptions<DatabaseConnections>>().Value;
var iDbConnections = resolver.GetServices<IDbConnection>();
databaseConnections.OracleConnections.ToList().ForEach(ora =>
{
ora.dbConnection = iDbConnections.Where(w => w.GetType() == typeof(OracleConnection)).FirstOrDefault();
ora.dbConnection.ConnectionString = ora.ConnectionString;
//ora.Guid = Guid.NewGuid();
});
databaseConnections.MSSqlConnections.ToList().ForEach(sql =>
{
sql.dbConnection = iDbConnections.Where(w => w.GetType() == typeof(SqlConnection)).FirstOrDefault();
sql.dbConnection.ConnectionString = sql.ConnectionString;
//sql.Guid = Guid.NewGuid();
});
return databaseConnections;
});
Above uses the Configuration class to map the appsettings.json section that houses your connection strings. Here's an example of the appsettings.json file:
"DatabaseConnections": {
"OracleConnections": [
{
"Alias": "TestConnection1",
"ConnectionString": "Data Source=(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP) (HOST = ) (PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = ) ) );User Id=;Password=;"
},
{
"Alias": "TestConnection2",
"ConnectionString": "Data Source=(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP) (HOST = ) (PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = ) ) );User Id=;Password=;"
}
],
"MSSqlConnections": [
{
"Alias": "Music",
"ConnectionString": "Data Source=(LocalDB)\\MSSQLLocalDB;AttachDbFilename=C:\\Users\\MusicLibrary.mdf;Integrated Security=True;Connect Timeout=30"
}
]
}
IOptions now gives me the ability to set my connection string at runtime in startup.cs close to the composition root.
Here's my class I'm using to map my connection strings:
public class DatabaseConnections : IDatabaseConnections
{
public IEnumerable<Connection> OracleConnections { get; set; }
public IEnumerable<Connection> MSSqlConnections { get; set; }
}
Now any service layer has access to multiple db connections and provider per request!
Github project: https://github.com/B-Richie/Dapper_DAL
This was solved for me in my program.cs file just adding this:
builder.Services.AddDbContext<YourContextClassHere>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("Web_Project_app_settings_connection_string_here"));
});
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"
}
I'm just starting with Asp.net core Dependency Injection, and my concept could be inaccurate. This docs.asp.net post explains how to inject context to a controller. I have little confusion regarding injection, in testing perspective. Assume we have following scenario:
public interface ITasksRepository
{
public void Create();
}
//This is fake implementation, using fake DbContext for testing purpose
public class TasksRepositoryFake : ITasksRepository
{
public void Create()
{
FakeDbContext.Add(sometask);
//logic;
}
}
//This is actual implementation, using actual DbContext
public class TasksRepository : ITasksRepository
{
public void Create()
{
DbContext.Add(someTask);
//logic;
}
}
Now in order to inject context in controller, we design it as:
public class TasksController : Controller
{
public ITasksRepository TaskItems { get; set; }
public TodoController(ITaskRepository taskItems)
{
TaskItems = taskItems;
}
//other logic
}
What asp.net core provides as builtin feature is, we can register the dependency injection in startup class as follows:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddSingleton<ITasksRepository, TasksRepositoryFake>();
}
According to this logic, my TaskRepositoryFake will be injected to the controller. So far, everything is clear. My questions/confusions regarding this are as follows:
Questions:
How can I use this builtin DI feature to inject the context using some logic? May be programatically, or configuration based, or environment based? (for example, always inject fake context, when using 'test' environment? etc.)
Is it even possible? If we always have to change this manually in StartUp class, then how does this builtin DI feature serve us? Because we could have simply done that in controller, without this feature.
First to answer your question: Yes, you can inject the dependency programmatically. By using factories, as is always the case with injecting dependencies based on run-time values. The AddSingleton has an overload which takes an implementationfactory and so a basic example for your use case looks like:
public class Startup
{
public bool IsTesting { get; }
public Startup(IHostingEnvironment env)
{
IsTesting = env.EnvironmentName == "Testing";
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISomeRepository>(sp => IsTesting ? (ISomeRepository)new SomeRepository() : (ISomeRepository) new FakesomeRepository());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, ISomeRepository someRepository)
{
app.UseIISPlatformHandler();
app.Run(async (context) =>
{
await context.Response.WriteAsync($"Hello World from {nameof(someRepository)}!");
});
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
The concerning line of code for your TasksRepository would look like:
services.AddSingleton<ITaskRepository>(sp => isTesting?(ITasksRepository)new TasksRepositoryFake(): (ITasksRespository)new TasksRepository() );
Even better would be to put it in a factory (again with my example):
services.AddSingleton<ISomeRepository>(sp => SomeRepositoryFactory.CreatSomeRepository(IsTesting));
I hope you see how this helps you setting it up config based, environment based, or however you want.
I you are interested I wrote more about DI based on run-time values via abstract factories here.
Having said that, with unit tests I would simply inject my fakes in the classes that are under test. Unit tests are there to still prove to yourself and your co-workers that the code still does as intended.
And with integration tests I would make a special StartUp class with all my fakes and give it to the test host as ASP.NET Core allows you to do. You can read more about the test host here: https://docs.asp.net/en/latest/testing/integration-testing.html
Hope this helps.
Update Added cast to interface because the ternary conditional has no way of telling. Plus added some basic samples.
You can inject your dependencies configuration based, or environment based, or both.
Option 1 : Environment Based
public IHostingEnvironment env{ get; set; }
public Startup(IHostingEnvironment env)
{
this.env = env;
}
public void ConfigureServices(IServiceCollection services)
{
if (env.IsDevelopment())
{
// register other fake dependencies
services.AddSingleton<ITasksRepository, TasksRepositoryFake>();
}
else
{
// register other real dependencies
services.AddSingleton<ITasksRepository, TasksRepository>();
}
}
Option 2 : Configuration Based
public IConfigurationRoot Configuration { get; set; }
public Startup()
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
var isFakeMode= Configuration["ServiceRegistrationMode"] == "Fake";
if (isFakeMode)
{
// register other fake dependencies
services.AddSingleton<ITasksRepository, TasksRepositoryFake>();
}
else
{
// register other real dependencies
services.AddSingleton<ITasksRepository, TasksRepository>();
}
}
Option 3 : Environment Based + Configuration Based
public IConfigurationRoot Configuration { get; set; }
public IHostingEnvironment env{ get; set; }
public Startup(IHostingEnvironment env)
{
this.env = env;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
var isFakeMode = Configuration["ServiceRegistrationMode"] == "Fake";
if (env.IsDevelopment() && isFakeMode)
{
// register other fake dependencies
services.AddSingleton<ITasksRepository, TasksRepositoryFake>();
}
else
{
// register other real dependencies
services.AddSingleton<ITasksRepository, TasksRepository>();
}
}