Resolution of IServiceProvider Services Lifetime within Injected Instance - c#

In my asp.net core I would like to inject IServiceProvider into conroller and resolve services explicitly.
What is the lifetime of the resolved services? Are they scoped in the controller's OR totally separated from it?
See MyController2 for actual code used:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(); //add mvc
services.AddControllers() // add all controller
services.AddTransient<IService1, Service1>
services.AddTransient<IService2, Service2>();
services.AddTransient<IService3, Service3>();
services.AddTransient<IService4, Service4>();
...
}
// ... rest of start up class
}
[ApiController]
[Route("[controller]"]
public class MyController1 : ControllerBase
{
private readonly IService1 _srv1;
private readonly IService2 _srv2;
private readonly IService3 _srv3;
private readonly IService4 _srv4;
public MyController1(IService1 srv1, IService2 srv2, IService3 srv3, IService4 srv4)
{
_srv1 = srv1; //gets controller's scope
_srv2 = srv2; //gets controller's scope
_srv3 = srv3; //gets controller's scope
_srv4 = srv4; //gets controller's scope
}
}
[ApiController]
[Route("[controller]"]
public class MyController2 : ControllerBase
{
private readonly IService1 _srv1;
private readonly IService2 _srv2;
private readonly IService3 _srv3;
private readonly IService4 _srv4;
public MyController1(IServiceProvider provider)
{
_srv1 = provider.GetService<IService1>(); // what is the scope???
_srv2 = provider.GetService<IService2>(); // what is the scope???
_srv3 = provider.GetService<IService3>(); // what is the scope???
_srv4 = provider.GetService<IService4>(); // what is the scope???
}
}

Related

Injecting logger from DI to object created with factory

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.

Create singleton when private constructor have parameters

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

Initialize object without pushing parameters

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()
}
}

.NET Core scoped dependency injection not working with MediatR

Consider the following code:
Startup.cs
public static void RegisterServices(IServiceCollection services)
{
services.AddSingleton<IEventBus, RabbitMQBus>();
services.AddTransient<IStuff, Stuff>(); // Empty/dummy interface and class
services.AddMediatR(typeof(AnsweredQuestionCommandHandler));
}
RabbitMQBus.cs
public sealed class RabbitMQBus : IEventBus
{
private readonly IMediator _mediator;
private readonly Dictionary<string, List<Type>> _handlers;
private readonly List<Type> _eventTypes;
private readonly IServiceScopeFactory _serviceScopeFactory;
public RabbitMQBus(IMediator mediator, IServiceScopeFactory serviceScopeFactory)
{
_mediator = mediator;
_serviceScopeFactory = serviceScopeFactory;
_handlers = new Dictionary<string, List<Type>>();
_eventTypes = new List<Type>();
}
public Task SendCommand<T>(T command) where T : Command
{
return _mediator.Send(command);
}
...
}
AnsweredQuestionCommandHandler.cs
public class AnsweredQuestionCommandHandler : IRequestHandler<QuestionAnsweredCommand, bool>
{
private readonly IEventBus _bus;
private readonly IStuff _stuff;
public AnsweredQuestionCommandHandler(IEventBus bus, IStuff stuff)
{
_bus = bus;
_stuff = stuff;
}
...
}
Can someone explain why injecting a Stuff with Transient or Singleton lifetime works as expected--when SendCommand() is invoked, the constructor for AnsweredQuestionCommandHandler is called, Stuff is injected--but if inject it with Scoped lifetime, not only is Stuff never injected, but in fact the constructor for AnsweredQuestionCommandHandler is never even called when SendCommand() is invoked?

Asp.net core DI with context

I'm trying implement a simple dependency (in ASP.NET Core) as this:
public partial class BaseController : Controller
{
public new ITempDataDictionary TempData { get; private set; }
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
//preparação da tempdata
this.TempData = new TempDataDictionary(HttpContext); //todo: DI?
this.TempData.Load();
}
}
}
The problem is the fact TempDataDictionary depends of HttpContext present in this controller.
How to implement that scenario in DI, since the ServiceLocator has no knowledge of HttpContext at Startup?
As this?
services.AddScoped(); //??????
But where i fill the constructor parameter HttpContext if this present just in controller?
You should create a service to handle your state data and add it as scoped.
public class AppStateService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ITempDataProvider _tempDataProvider;
private IDictionary<string, object> _data;
public AppStateService(IHttpContextAccessor httpContextAccessor, ITempDataProvider tempDataProvider, UserManager<EntsogUser> userManager, CompanyRepository companyRepository)
{
_httpContextAccessor = httpContextAccessor;
_tempDataProvider = tempDataProvider;
_data = _tempDataProvider.LoadTempData(_httpContextAccessor.HttpContext);
}
private void SetValue(string name, object value)
{
_data[name] = value;
_tempDataProvider.SaveTempData(_httpContextAccessor.HttpContext,_data);
}
private object GetValue(string name)
{
if (!_data.ContainsKey(name))
return null;
return _data[name];
}
}
In Startup.cs (ConfigureServices)
services.AddScoped<AppStateService>();
In your controller
public class TestController : Controller
{
protected readonly CompanyRepository _companyRepository;
public TariffsController(AppStateService appStateService)
{
_appStateService = appStateService;
}
}
you can take a dependency on IHttpContextAccessor and register it with DI
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
then use it to get the HttpContext
However in a controller you have direct access to HttpContext so it isn't clear to me why you would want to inject it there

Categories

Resources