In .NET framework I would have just used Server.MapPath(). In .NET Core 2.2, I understand I should be using IHostingEnvironment. But every example I can find seems to assume I'm using it in a controller. And these examples say to inject IHostingEnvironment in the controller. But...
In my case, I am using some *.json files as data repositories. I am creating an independent Data Access class that can be called from multiple upstream locations. I'm not grasping how to inject IHostingEnvironment into this class without impacting the upstream callers. The idea is, if we someday swap out the .json files for a SQL table, then we want to only make changes to the Data Access class, and not to any of the sources which call it.
So, how, in the Data Access class, can I map a path to a local .json file?
namespace DataAccess
{
public class Repository
{
private readonly string _connStringName;
public Repository(string connStringName)
{
_connStringName = connStringName;
}
public IEnumerable<T> GetList<T>()
{
IEnumerable<T> entities;
using (StreamReader r = new StreamReader(_connStringName)) // <---- ??
{
string json = r.ReadToEnd();
entities = JsonConvert.DeserializeObject<List<T>>(json);
}
return entities;
}
}
}
This line:
using (StreamReader r = new StreamReader(_connStringName))
wants to map a path to IIS Express, which I understand. But I need it to map a path to the relative location of the file.
Thanks!
EDIT:
Maybe I am doing this wrong? This is how I was trying to follow the examples I saw:
public class Repository
{
private readonly string _connStringName;
private IHostingEnvironment _hostingEnvironment;
public Repository(string connStringName, IHostingEnvironment environment)
{
_connStringName = connStringName;
_hostingEnvironment = environment;
}
// rest of the class
}
If I do it this way, every upstream location that creates an instance of Repository now has to pass `IHostingEnvironment' into it.
EDIT 2:
How Repository is being used.
public static class Customers
{
private static readonly string _connStringName = "customer.json";
public static string Get()
{
DataAccess.Repository repository = new DataAccess.Repository(_connStringName);
List<Customer> customers = repository.GetList<Customer>().ToList();
return JsonConvert.SerializeObject(customers);
}
}
In .NET Core you can register your dependencies to be injected at startup.
If you do it this way, you will need to:
inject Repository into Customers.
pass Repository dependencies in Startup
// Startup.cs
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services, IHostingEnvironment env)
{
// [2]
var connectionString = "customer.json"; // or get from a config file
services.AddSingleton<Repository>(new Repository(connectionString, env));
}
// ...
}
public class Customers
{
private Repository _repository;
public Customers(Repository repository)
{
// [1]
_repository = repository;
}
public string Get()
{
// you can use _repository here without passing stuff in
}
}
public class Repository
{
private IHostingEnvironment _env;
public Repository(string connectionString, IHostingEnvironment env)
{
_env = env;
}
}
.NET Core Dependency Injection - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2
Related
I'd like to access IWebHostEnvironment.WebRootPath anywhere in the asp.net core mvc application. For instance, some random class deep in the class hierarchy. Is there a static class or some other method to do so?
I am aware that I can inject IWebHostEnvironment or that I can cache the value on the startup of the application. My question is strictly about accessing it without these methods.
I am aware that I can inject IWebHostEnvironment or that I can cache the value on the startup of the application. My question is strictly about accessing it without these methods.
No, you cannot. There's no static built in here with access to this information. You can create your own though.
You can achieve this y doing the following
In your Shared project or common project which is reference by the Web project add the below interface
public interface IApplicationContext
{
public string BaseUrl { get; }
}
Then, in the web project add below code
public sealed class ApplicationContext : IApplicationContext
{
private readonly IWebHostEnvironment _webHostEnvironment;
private readonly IHttpContextAccessor _httpContextAccessor;
public ApplicationContext(IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContextAccessor)
{
_webHostEnvironment = webHostEnvironment;
_httpContextAccessor = httpContextAccessor;
}
public string BaseUrl
{
get
{
var baseUrl = _webHostEnvironment.IsDevelopment() ? AppConstants.BaseUrl.FELocalHostBaseUrl :
_httpContextAccessor.HttpContext?.Request.BaseUrl();
return baseUrl!;
}
}
}
Then, in you need to configure the dependency injection in your Startup.cs or any where that you configure DI as below
services.AddHttpContextAccessor();
services.AddScoped<IApplicationContext, ApplicationContext>();
Then you can inject the IApplicationContext in any service class constructor and access the baseUrl like below
public sealed class SecurityService
{
private readonly IApplicationContext _applicationContext;
public SecurityService(IApplicationContext applicationContext)
{
_applicationContext = applicationContext;
}
public async Task<ResponseResult> SendResetPasswordEmail(ForgotPasswordModel forgotPasswordModel, CancellationToken cancellationToken)
{
var baseUrl = _applicationContext.BaseUrl;
return new ResponseResult();
}
}
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 need the ASP.Net Core dependency injection to pass some parameters to the constructor of my GlobalRepository class which implements the ICardPaymentRepository interface.
The parameters are for configuration and come from the config file and the database, and I don't want my class to go and reference the database and config itself.
I think the factory pattern is the best way to do this but I can't figure out the best way to use a factory class which itself has dependencies on config and database.
My startup looks like this currently:
public class Startup
{
public IConfiguration _configuration { get; }
public IHostingEnvironment _environment { get; }
public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
_configuration = configuration;
_environment = environment;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IDbRepository, DbRepository>();
var connection = _configuration.GetConnectionString("DbConnection");
services.Configure<ConnectionStrings>(_configuration.GetSection("ConnectionStrings"));
services.AddDbContext<DbContext>(options => options.UseSqlServer(connection));
services.AddScoped<ICardPaymentRepository, GlobalRepository>();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IRFDbRepository rFDbRepository)
{
...
}
}
The GlobalRepository constructor looks like this:
public GlobalRepository(string mode, string apiKey)
{
}
How do I now pass the mode from configuration and the apiKey from the DbRepository into the constructor from Startup?
Use the factory delegate overload when registering the repository
//...
string mode = "get value from config";
services.AddScoped<ICardPaymentRepository, GlobalRepository>(sp => {
IDbRepository repo = sp.GetRequiredService<IDbRepository>();
string apiKey = repo.GetApiKeyMethodHere();
return new GlobalRepository(mode, apiKey);
});
//...
Alternative using ActivatorUtilities.CreateInstance
//...
string mode = "get value from config";
services.AddScoped<ICardPaymentRepository>(sp => {
IDbRepository repo = sp.GetRequiredService<IDbRepository>();
string apiKey = repo.GetApiKeyMethodHere();
return ActivatorUtilities.CreateInstance<GlobalRepository>(sp, mode, apiKey);
});
//...
You might want to also check these links...
https://github.com/Microsoft/AspNetCoreInjection.TypedFactories
https://espressocoder.com/2018/10/08/injecting-a-factory-service-in-asp-net-core/
With regard to the last link the code is basically:
public class Factory<T> : IFactory<T>
{
private readonly Func<T> _initFunc;
public Factory(Func<T> initFunc)
{
_initFunc = initFunc;
}
public T Create()
{
return _initFunc();
}
}
public static class ServiceCollectionExtensions
{
public static void AddFactory<TService, TImplementation>(this IServiceCollection services)
where TService : class
where TImplementation : class, TService
{
services.AddTransient<TService, TImplementation>();
services.AddSingleton<Func<TService>>(x => () => x.GetService<TService>());
services.AddSingleton<IFactory<TService>, Factory<TService>>();
}
}
I think castle windsor's typed factories dispose of all they created when they themselves are disposed (which may not be always the best idea), with these links you would probably have to consider if you are still expecting that behaviour. When I reconsidered why I wanted a factory I ended up just creating a simple factory wrapping new, such as:
public class DefaultFooFactory: IFooFactory{
public IFoo create(){return new DefaultFoo();}
}
I'll show the minimal example for the factory that resolves ITalk implementation by a string key. The solution can be easily extended to a generic factory with any key and entity type.
For the sake of example let's define the interface ITalk and two implementations Cat and Dog:
public interface ITalk
{
string Talk();
}
public class Cat : ITalk
{
public string Talk() => "Meow!";
}
public class Dog : ITalk
{
public string Talk() => "Woof!";
}
Now define the TalkFactoryOptions and TalkFactory:
public class TalkFactoryOptions
{
public IDictionary<string, Type> Types { get; } = new Dictionary<string, Type>();
public void Register<T>(string name) where T : ITalk
{
Types.Add(name, typeof(T));
}
}
public class TalkFactory
{
private readonly IServiceProvider _provider;
private readonly IDictionary<string, Type> _types;
public TalkFactory(IServiceProvider provider, IOptions<TalkFactoryOptions> options)
{
_provider = provider;
_types = options.Value.Types;
}
public ITalk Resolve(string name)
{
if (_types.TryGetValue(name, out var type))
{
return (ITalk)_provider.GetRequiredService(type);
}
throw new ArgumentOutOfRangeException(nameof(name));
}
}
Add extension method for simple implementations registration:
public static class FactoryDiExtensions
{
public static IServiceCollection RegisterTransientSpeaker<TImplementation>(this IServiceCollection services, string name)
where TImplementation : class, ITalk
{
services.TryAddTransient<TalkFactory>();
services.TryAddTransient<TImplementation>();
services.Configure<TalkFactoryOptions>(options => options.Register<TImplementation>(name));
return services;
}
}
And register the Cat and Dog implementations:
services
.RegisterTransientSpeaker<Cat>("cat")
.RegisterTransientSpeaker<Dog>("dog");
Now you can inject the TalkFactory and resolve the implementation by the name:
var speaker = _factory.Resolve("cat");
var speech = speaker.Talk();
The trick here is Configure<TOptions(). This method is additive, which means you can call it multiple times to configure the same instance of TalkFactoryOptions.
As I said this example can be converted into a generic factory and add the ability to register factory delegate instead of a concrete type. But the code will be too long for SO.
I've been running up against the same issue and solved this by registering a set of open generics for IFactory<TService>, IFactory<T, TService>, IFactory<T1, T2, TService> etc. A single call on startup to add this facility then allows any IFactory<...> to be injected / resolved, which will instantiate an instance of TService for a given set of argument types, provided a constuctor exists whose last parameters match the T* types of the factory generic. Source code, NuGet package and explanatory blog article below:
https://github.com/jmg48/useful
https://www.nuget.org/packages/Ariadne.Extensions.ServiceCollection/
https://jon-glass.medium.com/abstract-factory-support-for-microsoft-net-dependency-injection-3c3834894c19
An alternative to the other answers. Follow the options pattern.
First introduce a strong type for your configuration;
public class RespositoryOptions {
public string Mode { get; set; }
public string ApiKey { get; set; }
}
public GlobalRepository(IOptions<RespositoryOptions> options) {
// use options.Value;
}
You could still use a service factory method to unwrap the IOptions<RespositoryOptions> if you prefer. But then you lose the ability to verify that your service dependencies have all been met.
Then you can seed your options from configuration;
public void ConfigureServices(IServiceCollection services) {
...
services.Configure<RespositoryOptions>(_configuration.GetSection(name));
...
}
And write another service to update that options instance from other services, like a database;
public class ConfigureRespositoryOptions : IConfigureOptions<RespositoryOptions> {
private readonly IDbRepository repo;
public ConfigureRespositoryOptions(IDbRepository repo) {
this.repo = repo;
}
public void Configure(RespositoryOptions config) {
string apiKey = repo.GetApiKeyMethodHere();
}
}
Using Asp.Net Core we can make use of Dependency Injection in controllers/repositories.
However, I wish do do some logging in my Entity Class.
class Person
{
private ILogger<Person> _logger;
private List<Pets> pets;
public Person(ILogger<Person> logger)
{
_logger = logger;
}
public bool HasCat()
{
_logger.LogTrace("Checking to see if person has a cat.");
// logic to determine cat ownership
hasCat = true;
return hasCat;
}
}
When the Person class is instantiated by EntityFramework it does not attempt to inject any dependencies.
Can I force this? Am i going about it in completely the wrong way?
Ultimatley I just want to be able to use logging consistently throughout the application.
Thanks,
It is possible but I don't recommend it because I agree with commenters that logging belongs in your services and controllers.
EF Core 2.1 allows injecting the DbContext into a private constructor that EF will invoke. See the official docs.
First you need to expose a LoggerFactory property in your DbContext class.
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options, ILoggerFactory loggerFactory = null)
{
LoggerFactory = loggerFactory;
}
public ILoggerFactory LoggerFactory { get; }
}
Then you can inject the DbContext into a private constructor in your entity class.
public class Person
{
private readonly ILogger _logger;
public Person() { } // normal public constructor
private Person(MyDbContext db) // private constructor that EF will invoke
{
_logger = db.LoggerFactory?.CreateLogger<Person>();
}
public bool HasCat()
{
_logger?.LogTrace("Check has cat");
return true;
}
}
Something that used to be relatively easy, isn't so anymore. Of the dozens of times I've searched, I rarely find an answer to this situation, which I cannot believe is anything other than prevalent in most project structures.
I have the standard Core 2.0 Web app, and for now, for simplicity sake, an Infrastructure project and a unit test project. I have a good idea how to accomplish the test scenario since the test project doesn't run asp.net and I have a great video tutorial on how to accomplish it.
The problem lies with getting access to the DbContext in my infrastructure project. (.Net Core class library)
The DbContext sets up perfectly fine in Startup
var connString = Configuration.GetSection("ApplicationConfiguration:ConnectionStrings:DefaultConnection").Value;
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connString));
And in a controller I can access it
private ApplicationDbContext _context;
private IConfiguration Configuration { get; set; }
public HomeController(IConfiguration configuration, ApplicationDbContext context)
{
_context = context;
Configuration = configuration;
}
public IActionResult Index()
{
// gets users from the DI injected context in the controller
var users = _context.AppUsers.ToList();
// if GetUsers is defined statically, this doesn't work because the injected context is always null
//var diUsers = DatabaseService.GetUsers():
// making it non-static and newing it up works, but defeats the purpose because you are passing the context, asp.net is not injecting it
var ds = new DatabaseService(_context);
var diUsers = ds.GetUsers();
var svm = SettingsViewModel();
return View(svm);
}
DatabaseService
private ApplicationDbContext _context;
//this is the constructor for DatabaseService class
public DatabaseService(ApplicationDbContext context)
{
_context = context;
}
public List<ApplicationUser> GetUsers()
{
var users = _context.AppUsers.ToList();
return users;
}
Yes, I know I should be using a repository and I will once I get this figured out correctly. How do I set up my classes in the Infrastructure project so I have the injected DbContext created at Startup and not have to pass it as a parameter.
Addendum:
Using the answer provided by Nkosi, I can inject the data service in the controller and use it.
But if I have a separate Infrastructure project (Asp.net core 2 class library), which implements my repository and UoW
public class GenericRepository<T> : IRepository<T> where T : class
{
public GenericRepository()
{
}
//rest of code removed
}
How can I get the DbContext injected there? Do I need to create an Interface, IDbContext, to wrap DbContext, and register that in startup?
Assuming you have the following for your service
public interface IDatabaseService {
List<ApplicationUser> GetUsers();
//...
}
public class DatabaseService : IDatabaseService {
public DatabaseService(ApplicationDbContext context) {
//...code removed for brevity
}
//...code removed for brevity
}
The service explicitly depends on the ApplicationDbContext which will be injected when the implementation is being resolved from the service container.
Register the service in Startup.ConfigureServices
var connString = Configuration.GetSection("ApplicationConfiguration:ConnectionStrings:DefaultConnection").Value;
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connString));
services.AddScoped<IDatabaseService, DatabaseService>();
And refactor the controller to explicitly depend on the service
private IDatabaseService ds;
private IConfiguration Configuration { get; set; }
public HomeController(IConfiguration configuration, IDatabaseService ds) {
this.ds = ds;
Configuration = configuration;
}
public IActionResult Index() {
var diUsers = ds.GetUsers();
var svm = SettingsViewModel();
return View(svm);
}