I need to add logger (ILogger) to existing object of MyDbConnection, this object is created from Factory which is registered in NET Core DI together with MyOptions class
public class MyFactory : IMyFactory
{
private readonly MyOptions _options;
public MyFactory(MyOptions options)
{
_options = options;
}
public MyDbConnection CreateDbA() => new MyDbConnection(_options.ConnStrA);
public MyDbConnection CreateDbB() => new MyDbConnection(_options.ConnStrB);
public MyDbConnection CreateDbC() => new MyDbConnection(_options.ConnStrC);
}
Factory is then injected into service, which then use it to create object and do something
public class MyService : IMyService
{
public MyService(IMyFactory factory)
{
var a = factory.CreateDbA();
var b = factory.CreateDbB();
a.DoSomething();
b.DoSomething();
}
}
MyDbConnection looks like this:
public class MyDbConnection
{
private string connStr;
//private ILogger logger;
//public MyDbConnection(string connStr, ILogger logger)
public MyDbConnection(string connStr)
{
this.connStr = connStr;
//this.logger = Logger;
}
public void DoSomething()
{
//logger.LogWarning();
}
}
Problem is that I can't just add ILogger and inject it from DI container because factory is using 'new' to create MyDbConnection, without using DI. My only solution currently is to use static logger instead of injected one, but that seems to be a bad solution. Is there another way around this? How it should be done properly?
The design will need to be refactored to be able to get the desired behavior.
First MyDbConnection should be refactored accordingly to depend on the appropriate logger
For example
public class MyDbConnection {
private string connStr;
private ILogger logger;
public MyDbConnection(string connStr, ILogger<MyDbConnection> logger) {
this.connStr = connStr;
this.logger = logger;
}
public void DoSomething() {
logger.LogWarning();
}
}
Then the factory refactored to use ActivatorUtilities to initialize and inject the necessary dependencies via an injected service provider.
public class MyFactory : IMyFactory {
private readonly MyOptions options;
private readonly IServiceProvider services;
public MyFactory(MyOptions options, IServiceProvider services) {
this.options = options;
this.services = services;
}
public MyDbConnection CreateDbA() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrA);
public MyDbConnection CreateDbB() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrB);
public MyDbConnection CreateDbC() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrC);
}
In the example above the specific dependencies are provided just as before when they were manually initialized. All other dependencies (like the logger) will be resolved via the service provider and injected into the target class when it is being initialized.
This way dependency injection can be maintained and manual initialization of MyDbConnection can be avoided.
Related
I would like to implement singleton pattern in StudentProvider and then access method through interface. StudentProvider constructor accepts few parameters. Here's the sample working code without singleton.
public interface IStudentProvider
{
Task<StudentViewModel> GetStudentAsync();
}
public class StudentProvider : IStudentProvider
{
private readonly HttpContext httpContext;
private readonly IActionContextAccessor actionContextAccessor;
private readonly IConfiguration configuration;
private readonly IUnitOfWork unitOfWork;
private readonly string host;
public StudentProvider(IHttpContextAccessor _httpContextAccessor, IActionContextAccessor _actionContextAccessor, IConfiguration _configuration, IUnitOfWork _unitOfWork)
{
httpContext = _httpContextAccessor.HttpContext;
actionContextAccessor = _actionContextAccessor;
configuration = _configuration;
unitOfWork = _unitOfWork;
host = _httpContextAccessor.HttpContext.Request.Host.Host;
}
public async Task<StudentViewModel> GetStudentAsync()
{
var std = new StudentViewModel();
// httpContext, actionContextAccessor, configuration, unitOfWork and host uses here
return std;
}
}
Now i converted this into single, here's the code:
public interface IStudentProvider
{
Task<StudentViewModel> GetStudentAsync();
}
public sealed class StudentProvider : IStudentProvider
{
private readonly HttpContext httpContext;
private readonly IActionContextAccessor actionContextAccessor;
private readonly IConfiguration configuration;
private readonly IUnitOfWork unitOfWork;
private readonly string host;
private static StudentProvider instance = null;
public static StudentProvider GetInstance
{
get
{
if (instance == null)
{
instance = new StudentProvider();
}
return instance;
}
}
private StudentProvider(IHttpContextAccessor _httpContextAccessor, IActionContextAccessor _actionContextAccessor, IConfiguration _configuration, IUnitOfWork _unitOfWork)
{
httpContext = _httpContextAccessor.HttpContext;
actionContextAccessor = _actionContextAccessor;
configuration = _configuration;
unitOfWork = _unitOfWork;
host = _httpContextAccessor.HttpContext.Request.Host.Host;
}
public async Task<StudentViewModel> GetStudentAsync()
{
var std = new StudentViewModel();
// httpContext, actionContextAccessor, configuration, unitOfWork and host uses here
return std;
}
}
The issue with above singleton code is instance = new StudentProvider(); is expecting parameters which i'm not able to pass.
How do i pass parameters to constructor from singleton instance ?
It seems that you're using ASP.NET and it's dependency injection. If so, you can use AddSingleton to register your provider instead of implementing your own singleton pattern. Singleton.
BTW, your provider depends on a HttpContext which means you need to create different instance for different requests.
As #Jon Skeet suggested, it will be better to use Dependency Injection.
I will also recommend to #Xiaofeng Zheng solution to use the singleton dependency injection with factory pattern.
And if all these does not satisfy, you can go with below solution.
You will need to keep the reference of IServiceProvider as singleton in your Startup file which can be accessed globally.
public class Startup
{
public static IServiceProvider ServiceProvider { get; private set; }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider) {
...
ServiceProvider = serviceProvider;
}
}
Then, you can access the Startup.ServiceProvider within your StudentProvider to create the instance of other dependencies.
using Microsoft.Extensions.DependencyInjection;
public sealed class StudentProvider : IStudentProvider
{
private readonly HttpContext httpContext;
private readonly IActionContextAccessor actionContextAccessor;
private readonly IConfiguration configuration;
private readonly IUnitOfWork unitOfWork;
private readonly string host;
private static StudentProvider instance = null;
public static StudentProvider GetInstance
{
get
{
if (instance == null)
{
instance = new StudentProvider(
Startup.ServiceProvider.GetService<IHttpContextAccessor>(),
Startup.ServiceProvider.GetService<IActionContextAccessor>(),
Startup.ServiceProvider.GetService<IConfiguration>(),
Startup.ServiceProvider.GetService<IUnitOfWork>()
);
}
return instance;
}
}
private StudentProvider(IHttpContextAccessor _httpContextAccessor, IActionContextAccessor _actionContextAccessor, IConfiguration _configuration, IUnitOfWork _unitOfWork)
}
I have class with constructor for logging and for access to config:
public class SendEmaiServiceProvider
{
private readonly IConfiguration _config;
private readonly IWebHostEnvironment _env;
private readonly ILogger<SendEmaiServiceProvider> _logger;
private readonly string _fromEmailAddress;
public SendEmaiServiceProvider(IConfiguration config, IWebHostEnvironment env, ILogger<SendEmaiServiceProvider> logger)
{
_config = config;
_env = env;
_logger = logger;
_fromEmailAddress = _config.GetValue<string>("AppSettings:Email:FromEmailAddress");
}
public void SayHi()
{
Console.WriteLine("Hi");
}
}
The question is - How to call method SayHi from another class without pushing logger, env and config?
No I initialize new object with parameters, but I sure that it is wrong:
var sendEmaiServiceProvider = new SendEmaiServiceProvider(_config, _env, _logger);
sendEmaiServiceProvider.SayHi();
I can create an empty constructor but I will not have _fromEmailAddress value.
Looks like this is a netcore website. Assuming so, then:
Create an interface for the dependency.
Register the dependency in Startup.cs
Request the dependency as needed from the netcore DI.
public interface ISendEmaiServiceProvider
{
void SayHi()
}
public class SendEmaiServiceProvider : ISendEmaiServiceProvider
{
public void SayHi() { }
}
Then in Startup.cs:
public void ConfigureServices( IServiceCollection services )
{
services.AddScoped<ISendEmaiServiceProvider, SendEmaiServiceProvider>();
}
Then in the Controller (or wherever else DI is used), request it in the .ctor and all the dependencies for SendEmaiServiceProvider will be filled automatically by DI.
public class HomeController : Controller
{
public readonly ISendEmaiServiceProvider _emailService;
public HomeController( ISendEmaiServiceProvider emailService )
{
_emailService = emailService
}
}
That should get you going.
You should use dependency injection here. Better you create an interface here and resolve your 'SendEmaiServiceProvider' on the startup. And then use the interface instead of creating a new instance for SayHi() method.
public interface YourInterface
{
void SayHi()
}
public class SendEmaiServiceProvider : YourInterface
{
public void SayHi()
{
//your code
}
}
On your startup,
public void ConfigureServices( IServiceCollection services )
{
services.AddScoped<YourInterface, SendEmaiServiceProvider>();
}
On your controller/service,
public class YourController : Controller
{
public readonly YourInterface _emailSenderService;
public HomeController( YourInterface emailSenderService )
{
_emailSenderService = emailSenderService
}
public IActionResult SayHI()
{
_emailSenderService.SayHi()
}
}
I have a question related to the use of database contexts outside the controller, namely, how to call the database context in a regular class?
To communicate with the database, I use: EF Core
I used options like this:
private readonly MSSQLContext _context;
public BookingController(MSSQLContext context)
{
_context = context;
}
Alternative
using (MSSQLContext context=new MSSQLContext())
{
context.get_Users.ToList();
}
Startup.cs
services.AddDbContext<MSSQLContext>(options =>
options.UseSqlServer(connection));
MSSQLContext.cs
public MSSQLContext()
{
}
public MSSQLContext(DbContextOptions<MSSQLContext> options)
: base(options)
{
}
public DbSet<VIVT_Core_Aud.Models.Core.Logger_Model> Logger_Models { get; set; }
and more tables...
Inject the context into whatever class you need to call into and register that class with the DI framework in your startup class.
For instance,
services.AddTransient<YourType>();
class YourType
{
public YourType(YourDbContext context) { ... }
}
You need to inject context using DI(dependency injection). I am showing you an example of repository pattern. You can search for "Repository pattern EF Core C# examples" will give you lots of examples or blogs to follow. Check here and here
Check out below example..
MyRepository.cs
namespace MyApp.Data.Services
{
public class MyRepository: IMyRepository, IDisposable
{
private MyAppContext _context;
private readonly ILogger<MyRepository> _logger;
public MyRepository(MyAppContext context, ILogger<MyRepository> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger;
}
public IEnumerable<MyTable> GetMyTableData()
{
return _context.MyTable.ToList();
}
}
}
IMyRepository.cs
namespace MyApp.Data.Services
{
public interface IMyRepository
{
//Interface implementation
IEnumerable<MyTable> GetMyTableData();
}
}
Startup.cs of MVC project
services.AddDbContext<MyAppContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("AppDbConnString")));
//Scope of repository should be based on your project used, I recommend to check lifetime & scope based your project needs.
services.AddScoped<IMyRepository, MyRepository>();
Mycontroller.cs
using MyApp.Data.Services;
namespace MyApp.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IMyRepository _myRepository;
public HomeController(ILogger<HomeController> logger, IMyRepository myRepository)
{
_logger = logger;
_myRepository = myRepository ??
throw new ArgumentNullException(nameof(myRepository));
}
public IActionResult Index()
{
return View();
}
public IActionResult GetAllData()
{
var result = _myRepository.GetMyTableData();
return Json(result);
}
}
}
I want to pass a logger into a constructor of an object. Normally, the logger is dependency injected into a class, but I want to log inside a class that is not dependency injected.
I have a .NET Core worker service. In that service, I have 0-many instances of a class that are created at runtime. I also want to be able to log messages to the logger inside that class.
In my example, I can pass the ILogger that I get from inside the TimedWorker, but the type is wrong.
Currently, I have:
class TimedWorker : BackgroundService
{
private List<Dog> m_dogs;
private ILogger<TimedWorker> m_logger;
private IConfiguration m_configuration;
public TimedWorker(ILogger<TimedWorker> logger, IConfiguration configuration)
{
m_logger = logger;
m_configuration = configuration;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
m_dogs= new List<Dogs>();
List<DogOption> dogOptions= new List<DogOption>();
m_configuration.GetSection("Dogs").Bind(dogOptions);
foreach(DogOption option in dogOptions)
{
m_dogs.Add(new Dog(option, m_logger));
}
}
///...
}
class Dog {
public string Name { get; }
private int Age;
private ILogger m_logger;
public Dog(DogOption option, ILogger logger)
{
m_logger = logger;
Name = option.Name;
Age = option.Age;
m_logger.LogInformation($"Dog '{Name}' created");
}
}
The problem is that when it logs, the logger is of type TimedWorker, not Dog. So when I look at the logs, they are labeled under TimedWorker instead of Dog. I do not know how to pass in a ILogger into the Dog class. Any advice?
You can inject the IServiceProvider and resolve the dependency manually.
You can do it like this:
public class Worker
{
private readonly IServiceProvider _serviceProvider;
public Worker(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Start()
{
var specificLogger = _serviceProvider.GetService(typeof(ILogger<Dog>)) as ILogger<Dog>;
var dog = new Dog(specificLogger);
}
}
public class Dog
{
public Dog(ILogger<Dog> logger)
{
logger.LogInformation("Hello");
}
}
This creates the specific log based on the class.
2020-11-18 18:41:58.1275||INFO|ConsoleApp.Dog|Hello
You can create a logger directly. For example:
var logger = LoggerFactory.Create(options => {}).Create<Worker>();
I have a base controller (i didn't create it btw) in my net core api that basically starts with following:
public abstract class MyBaseController<T> : ControllerBase where T : MyBaseController<T>
{
private ILogger<T> _logger;
protected ILogger<T> Logger => _logger ?? (_logger = HttpContext?.RequestServices.GetService<ILogger<T>>());
}
When i am unit testing my other controller that inherits the base controller how do deal with this logger?
currently my unit test class has a constructer with something like
_controller = new cartController(_cartService);
but then i get stuck.
I will be using xUnit and Moq in the test project.
Any help is appreciated.Thanks
Here's a minimal example from this article on how to inject an ILogger dependency and then verify a call afterward with Moq:
public class LogTest
{
private readonly ILogger _logger;
public const string InformationMessage = "Test message";
public const string ErrorMessage = "Not implemented {recordId}";
public LogTest(ILogger<LogTest> logger)
{
_logger = logger;
}
public void Process()
{
_logger.LogInformation(InformationMessage);
}
}
_loggerMock.Verify(l => l.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()), Times.Exactly(1));
In general you should rely on DI in tests as well as in runtime. The following library contains a test logger which you may use in tests: https://www.nuget.org/packages/com.github.akovac35.Logging.Testing/
Usage samples are available here: https://github.com/akovac35/Logging.Samples
Disclaimer: I am the author of the above.
Basically you would proceed as follows:
Use NullLogger by default:
public abstract class MyBaseController<T> : ControllerBase
{
private ILogger _logger = NullLogger.Instance;
protected MyBaseController(ILogger<MyBaseController<T>> logger = null)
{
if (logger != null) _logger = logger;
}
}
Derived classes should inject logger:
public class MyBaseControllerVariant<T> : MyBaseController<T>
{
private ILogger _logger = NullLogger.Instance;
public MyBaseControllerVariant(ILogger<MyBaseControllerVariant<T>> logger = null, ILogger<MyBaseController<T>> baseLogger = null): base(baseLogger)
{
if (logger != null) _logger = logger;
}
}
Now wire up everything:
using com.github.akovac35.Logging.Testing;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Shared.Mocks;
using System;
namespace TestApp
{
[TestFixture]
public class TestLoggingExamples
{
[OneTimeSetUp]
public void OneTimeSetUp()
{
customOnWrite = writeContext => {
Console.WriteLine(writeContext);
};
customOnBeginScope = scopeContext => {
Console.WriteLine(scopeContext);
};
serviceCollection = new ServiceCollection();
serviceCollection.AddTransient(typeof(MyBaseControllerVariant<>));
// Register TestLogger using extension method
serviceCollection.AddTestLogger(onWrite: customOnWrite, onBeginScope: customOnBeginScope);
}
private IServiceCollection serviceCollection;
private Action<WriteContext> customOnWrite;
private Action<ScopeContext> customOnBeginScope;
[Test]
public void Test_WithLoggingToTestConsole_Works()
{
// The service provider should be defined on per-test level or logger writes will accumulate and may result in OOM - clean them with testSink.Clear()
var serviceProvider = serviceCollection.BuildServiceProvider();
var controller = serviceProvider.GetRequiredService<MyBaseControllerVariant<object>>();
controller.Invoke();
var testSink = serviceProvider.GetRequiredService<ITestSink>();
Assert.IsTrue(testSink.Writes.Count > 0);
Assert.IsTrue(testSink.Scopes.Count > 0);
}
}
}