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;
}
}
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 am building a Web API in ASP.NET Core 3.1 with Entity Framework.
My database context is registered as a service in the Startup.cs:
services.AddDbContext<LocalDbContext>(options =>
{
options.UseSqlServer(builderLocal.ConnectionString);
});
And I am retrieving the DbContext in my controller using DI, passing it into my database-access-class (DAL) when I instantiate it for each method
private readonly LocalDbContext _context;
public HomeController(LocalDbContext context)
{
_context = context;
}
public IActionResult GetSomeData(int id)
{
var localDb = new LocalDb(_context);
return Ok(localDb.GetSomeDataById(id));
}
And then my Database file:
public class LocalDbContext : DbContext
{
public LocalDbContext(DbContextOptions<LocalDbContext> options)
: base (options) { }
**DbSets for my models**
}
public class LocalDb
{
private readonly LocalDbContext _context;
private readonly ILogger<LocalDb> _logger;
// I would want to avoid having to pass in logger in this contstructor
public LocalDb(LocalDbContext context)
{
_context = context;
// Can I retrieve a logger from somewhere else? From the context?
// _logger = logger;
}
public void AddStudent(Student student)
{
_context.Student.Add(student);
try
{
_context.SaveChanges();
}
catch (Exception ex)
{
_logger.LogError("logMessage about exception: " + ex.StackTrace);
throw;
}
}
}
So I wish to have the _logger available to write to, but in my code I have not set the _logger to anything. I know ILogger exists in the DbContext, and there is lots of guides explaining how to add and configure the logger for the Context and EF. But when I want to use my LocalDb class, do I have to pass in a ILogger instance for every time i call var localDb = new LocalDb(_context, _logger)
I feel like having to pass in a logger instance every time is a bit strange, there must be a better solution to this. Would it be possible to add the LocalDb class as a service, then dependency inject context and logger into that class? Whats the best practice?
Notice that HomeController doesn't use LocalDbContext directly, but only uses it to pass it on to its real dependency, LocalDb. Therefore, you should not inject LocalDbContext into HomeController's constructor, but instead inject LocalDb directly. This solves your problems elegantly, because now LocalDb can be registered in the DI Container, and it can resolve it for you with any dependency it might have.
Here's an example of a HomeController that depends on LocalDb directly.
public class HomeController : Controller
{
private readonly LocalDb _db;
public HomeController(LocalDb db)
{
_db = db;
}
public IActionResult GetSomeData(int id)
{
return Ok(_db.GetSomeDataById(id));
}
}
Because LocalDb is injected into the constructor, it must be registered in the DI Container:
services.AddTransient<LocalDb>();
services.AddDbContext<LocalDbContext>(options =>
{
options.UseSqlServer(builderLocal.ConnectionString);
});
But since LocalDb is composed by the DI Container, it can be extended with any dependencies, such as the ILogger dependency:
public class LocalDb
{
private readonly LocalDbContext _context;
private readonly ILogger<LocalDb> _logger;
public LocalDb(LocalDbContext context, ILogger<LocalDb> _logger)
{
_context = context;
_logger = logger;
}
...
}
TIP: Prevent sprinkeling catch statements across your code base that log and rethrow. Prefer instead to have some global infrastructure that logs any requests that have failed. If I'm not mistaken ASP.NET Core does this for you out of the box. If not, this can be enabled with just a few lines of code. This makes code (such as your LocalDb.AddStudent much simpler, and limits the number of dependencies that a class has.
I have this piece of code in the startup.cs file of my ASP.Net Core Web API project:
public void ConfigureServices(IServiceCollection services)
{
// Retrieve the database connection string
string connectionString = "Do something to retrieve a connection string";
services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));
}
I have reasons to do this here and not in MyContext's OnConfiguring() method, but now I am getting this error:
System.InvalidOperationException: 'No database provider has been
configured for this DbContext. A provider can be configured by
overriding the DbContext.OnConfiguring method or by using AddDbContext
on the application service provider. If AddDbContext is used, then
also ensure that your DbContext type accepts a
DbContextOptions object in its constructor and passes it to
the base constructor for DbContext.'
This is my MyContext class's:
public partial class MyContext : DbContext
{
public MyContext()
{
}
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
...
}
I found somewhere that I also need to do this:
public class MyController : Controller
{
private MyContext _context;
public MyController(MyContext context)
{
_context = context;
}
}
but this is very inconvenient since I am currently not instantiating MyContext in the controllers, but in a different layer, for example:
public ActionResult MyMethod(...)
{
MyManager.DoSomething(); // MyManager instantiates the context
return Ok();
}
This is how I'm currently instantiating the context:
private static readonly MyContext myContext = new MyContext();
I'm guessing I need to somehow inject the options into the context, but I don't know how.
Dependency injection works that way, by injecting via constructor (recommended approach which leads to easier to test code and ensure invariants). You should just use that.
but this is very inconvenient since I am currently not instantiating MyContext in the controllers, but in a different layer, for example:
This seems to be a wrong assumption on your side that injection only works in constructor.
Any service registered with the DI can have stuff injected into it, when resolved. So if you use your MyContext in a service class, inject it there and inject the service into your controller.
Mind the lifetimes though. AddDbContext adds the context with scoped life time, means it will get disposed at the end of the request. This is by design (can be override with one of the AddDbContext overloads), since EF Core is tracking changes in memory and unless disposed can lead to memory leakage and high memory consumption.
It seems that you are registering your context to services collection and it should work if your get your context from there.
But you're simply creating a new unconfigured DbContext instance and so you get an error that it's not configued:
private static readonly MyContext myContext = new MyContext();
Solution: Let the context be injected via DI: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1
public class MyServiceThatNeedsDbContext {
private readonly MyContext _myContext;
MyServiceThatNeedsDbContext(MyContext myContext) {
_myContext = myContext;
}
}
You'll have to register your service class to services collection and get instances from there to make it work.
A simple registration could look like this (Startup.cs):
services.AddTransient<IMyServiceThatNeedsDbContext, MyServiceThatNeedsDbContext>();
.. and in your controller
public class MyController : Controller
{
private IMyServiceThatNeedsDbContext _myService;
public MyController(IMyServiceThatNeedsDbContext myService)
{
_myService = myService;
}
}
I would suggest removing parametherless constructor because there is known issue where that's called instead of one with options in it.
Add in yourDbContext
// overload constructer
public MyContext(DbConfig dbconfig)
{
_dbconfig = dbconfig
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
switch (_dbConfig.DbType)
{
case DbTypes.MsSql:
optionsBuilder.UseSqlServer(_dbConfig.ConnectionString);
break;
case DbTypes.PostgreSql:
optionsBuilder.UseNpgsql(_dbConfig.ConnectionString);
break;
case DbTypes....
.....
}
optionsBuilder.EnableSensitiveDataLogging();
}
}
and Create
public class DbConfig
{
public DbTypes DbType { get; set; }
public string ConnectionString { get; set; }
}
public enum DbTypes
{
MsSql,
PostgreSql,
.....,
....
}
then create global _dbConfig in MyManager and configure dbtype and constring
and then
private yourDbContext GetNewDbContext()
{
return new yourDbContext(_dbConfig);
}
inject in MyManager
Try Unity containers to access your instance when your controller is called.
https://www.tutorialsteacher.com/ioc/unity-container
The context will be provided automatically through dependency injection after registering from your UnityConfig. (The nuget puts in the code.)
public static void Register(HttpConfiguration httpConfiguration)
{
httpConfiguration.DependencyResolver = new UnityDependencyResolver(Container);
}
I have some problem with DI (Dependancy Injection). My project is on netcore 2.0 and has few layers (standard N-Tier Architecture). I'm trying to move EF Core 2 from Presentation Layer to Data Access Layer and I have created the following classes in DAL:
namespace MyProject.Infrastructure.Implementation.MySql.Contexts
{
public class ApplicationDbContext : DbContext
{
private readonly IConfiguration _configuration;
public ApplicationDbContext(IConfiguration configuration)
{
_configuration = configuration;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql(
Configuration.GetConnectionString("MySql")
);
}
public DbSet<Test> Test { get; set; }
}
}
Then I prepared base class for all DAL engines:
namespace MyProject.Infrastructure.Implementation.MySql
{
public class BaseEngine : IBaseEngine
{
private readonly ApplicationDbContext _context;
protected ApplicationDbContext Db => _context;
public BaseEngine(ApplicationDbContext context)
{
_context = context;
}
}
}
So, my common engine should look like this:
namespace MyProject.Infrastructure.Implementation.MySql
{
public class TestEngine : BaseEngine
{
public List<Test> GetTestList()
{
return Db.Test.ToList();
}
}
}
The problem is that I get error, BaseEngine needs parameter to be passed in constructor and I don't want to create all instances manually, I need somehow use Dependancy Injection that automatically creates instances of ApplicationDbContext and IConfiguration when BaseEngine and ApplicationDbContext will be created..
Any ideas?
Create a public interface for ApplicationDbContext, like IApplicationDbContext. Put that in the constructor for BaseEngine instead of the concrete class. Make the BaseEngine constructor protected. The BaseEngine constructor should look like:
protected BaseEngine(IApplicationDbContext context)
Then, since TestEngine is derived from BaseEngine, and BaseEngine requires a constructor argument, you have to pass that in from the TestEngine constructor like:
public TestEngine(IApplicationDbContext context) : base(context)
I used to have a UserFactory (before vNext) that used HttpContext.Current but since that is now gone I am having trouble recreating it.
I want it to be a static class that sets and gets the current user to access user data throughout the application.
I know I must use the DI system but not sure how.
Code so far:
public class CurrentContext : IHttpContextAccessor
{
private IHttpContextAccessor ctx;
public HttpContext HttpContext
{
get
{
return ctx.HttpContext;
}
set
{
ctx.HttpContext = value;
}
}
}
services.AddTransient<IHttpContextAccessor, CurrentContext>();
public class UserFactory
{
private IHttpContextAccessor _context;
public UserFactory(IHttpContextAccessor Context)
{
_context = Context;
}
public void Add(string s) => _context.HttpContext.Session.SetString(s, s);
public string Get(string s) => _context.HttpContext.Session.GetString(s);
}
How can I get a UserFactory instance anywhere in my app with the current context?
I suggest you make the UserFactory class non-static and register it as scoped:
services.AddScoped<UserFactory>();
This will create one instance per web request. You can inject this into every other class and let the UserFactory take a dependency on IHttpContextAccessor to get the current HttpContext.
This adheres to the dependency inversion philosophy Microsoft is trying to implement in ASP.NET 5. Static classes do not really fit into this and should be avoided as much as possible.
Example
UserFactory class:
public class UserFactory
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserFactory(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
// other code...
}
ConfigureServices() in Startup class:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddScoped<UserFactory>();
// ...
}
Now you can inject an instance of UserFactory in a controller for example:
public class SomeController : Controller
{
private readonly UserFactory _userFactory;
public SomeController(UserFactory userFactory)
{
_userFactory = userFactory;
}
// ...
}
Now when UserFactory is begin resolved, IHttpContextFactory inside UserFactory will also be resolved.