Any way for long living log scopes in .NET - c#

I have the need to add information to the log scopes in .NET and they need to survive longer than a single method call...
Usualy the samples always tell us to use log scopes like this
public void DoSomething()
{
using(Logger.BeginScope("Instance id {Guid}", strGuid)
{
Logger.LogInformation("did something");
}
}
This would need to wrap every public accessible with the begin scope call....
public class SampleServivce : IDisposable
{
public readonly ILogger<SampleService> Logger;
private IDisposable _logScope;
public SampleService(ILogger<SampleService> logger)
{
Logger = logger;
_logScope = Logger.BeginScope("Instance id {Guid}", Guid.NewGuid().ToString();
}
public void DoSomething()
{
Logger.LogInformation("did something");
}
public void Dispose()
{
logScope?.Dispose();
}
}
The second idea would be to create the scope in the constructor and dispose it in the dispose method. And this is where the
This post gives greater explanation why this would not work and yes, I can confirm this won't work because I am currently troubleshooting "wrong log scope info".
What our setup extends is the layer we have multiple instances of background services waiting for and doing tasks up on demand.
Unfortunatly some scoped services required us to create dedicated service scopes per instance of background workers and requesting the services on demand...
So we do ...
public class BackgroundWorker1 : BackgroundService
{
private string _workerInstance = Guid.NewGuid().ToString();
private IDisposable _logScope;
private IServiceScope _serviceScope;
public readonly IServiceScopeFactory ServiceScopeFactory;
public readonly ILogger<BackgroundWorker1> Logger;
private IServiceProvider _serviceProvider;
public BackgroundWorker1(IServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_serviceScope = ServiceScopeFactory.CreateScope();
_serviceProvider = _serviceScope.ServiceProvider;
Logger = _serviceProvider.GetRequiredService<ILogger<BackgroundWorker1>>();
_logScope = Logger.BeginScope("worker instance id {WorkerId}", _workerInstance);
}
public void OneOfManyExternallyCalledMethods()
{
var service = _serviceProvider.GetRequiredService<Some3rdPartyService>();
service.DoSomething();
// all logs of all (incl. 3rd party/Microsoft) libs should contain the guid of the worker instance
}
public void Dispose()
{
_serviceScope?.Dispose();
_logScope?.Dispose();
}
}
The ultimate goal is to have each worker instance id in each log ....
and in some services the services (we can rewrite) the serivce instance id from the second sample ....

Related

Cannot access a disposed context instance with N-layer architecture

I'm trying to make a N-layer architecture for my Telegram Bot. I created DAL, BLL and PL. I would like to add entity News to my DB. But I have some issue with my context.
My DB Context:
public class ApplicationContext : DbContext
{
public DbSet<News> News { get; set; }
public DbSet<User> Users { get; set; }
public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<News>().Property(tn => tn.Id).ValueGeneratedOnAdd();
modelBuilder.Entity<User>().Property(tn => tn.Id).ValueGeneratedOnAdd();
modelBuilder.Entity<News>().Property(tn => tn.Title).IsRequired();
modelBuilder.Entity<News>().Property(tn => tn.Href).IsRequired();
modelBuilder.Entity<News>().Property(tn => tn.Image).IsRequired();
modelBuilder.Entity<News>().Property(tn => tn.Date).IsRequired();
modelBuilder.Entity<User>().Property(tn => tn.UserId).IsRequired();
modelBuilder.Entity<User>().Property(tn => tn.UserName).IsRequired();
modelBuilder.Entity<User>().Property(tn => tn.DateOfStartSubscription).IsRequired();
base.OnModelCreating(modelBuilder);
}
}
Interface UoW:
public interface IUnitOfWork : IDisposable
{
INewsRepository News { get; }
IUserRepository Users { get; }
int Complete();
}
Class UoW:
public class UnitOfWork : IUnitOfWork
{
public IUserRepository Users { get; }
public INewsRepository News { get; }
private readonly ApplicationContext _context;
public UnitOfWork(ApplicationContext context)
{
_context = context;
Users = new UserRepository.UserRepository(_context);
News = new NewsRepository.NewsRepository(_context);
}
public int Complete() => _context.SaveChanges();
public void Dispose() => _context.Dispose();
}
My DAL Generic Repository:
async Task IGenericRepository<T>.AddAsync(T entity) => await _context.Set<T>().AddAsync(entity);
DAL Injection:
public static class DALInjection
{
public static void Injection(IServiceCollection services)
{
services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>));
services.AddTransient<IUserRepository, UserRepository.UserRepository>();
services.AddTransient<INewsRepository, NewsRepository.NewsRepository>();
services.AddTransient<IUnitOfWork, UnitOfWork.UnitOfWork>();
}
}
My BLL Service class:
public class ParserService : IParser
{
private IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public ParserService(IUnitOfWork unitOfWork, IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
private async Task SaveArticles(IEnumerable<NewsDTO> articlesDTO)
{
var articles = _mapper.Map<IEnumerable<NewsDTO>, IEnumerable<News>>(articlesDTO);
await _unitOfWork.News.AddAsync(articles.First());
_unitOfWork.Complete();
}
BLL Injection:
public static class BLLInjection
{
public static void Injection(IServiceCollection services)
{
DALInjection.Injection(services);
services.AddTransient<IParser, ParserService>();
services.AddTransient<IArticleService, ArticleService>();
services.AddAutoMapper(typeof(CommonMappingProfile));
}
}
My PL:
private static async Task SendArticleAsync(long chatId, int offset, int count)
{
var articles = await _parser.MakeHtmlRequest(offset, count);
foreach (var article in articles)
{
var linkButton = KeyboardGoOver("Перейти", article.Href);
await _client.SendPhotoAsync(chatId: chatId, photo: article.Image,
caption: $"*{article.Title}*", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: linkButton);
}
await OnLoadMoreNewsAsync(chatId, offset + count, count);
}
PL Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<ApplicationContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationContext).Assembly.FullName)));
BLLInjection.Injection(services);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TelegramBot.WebApi", Version = "v1" });
});
}
When I tried to debug, I had this error but I could not resolve this issue.
_context = Database = {"Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may o...
Could someone help me with this issue?
There are few problems in your code.
Controllers are scoped entities, their instances created per http request and disposed after request is finished. It means controller is not good place to subscribe to events. When you call /start endpoint you create an instance of TelegramController and TelegramBotClient, but once the request is finished, the controller and all its non-singleton dependencies (IParser in your case) are disposed. But you subscribed for TelegramBotClient events that captured reference to IParser. It means all events that will arrive after request is finished will try to access disposed IParser instance and this is the reason for your exception.
For event based messages it's better to use IHostedService. You will need to use IServiceScopeFactory to create a scope for each message and resolve your dependencies from this scope.
public class TelegramHostedService : IHostedService
{
private IServiceScopeFactory _scopeFactory;
public TimedHostedService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_client = new TelegramBotClient(_token);
_client.OnMessage += OnMessageHandlerAsync;
_client.OnCallbackQuery += OnLoadCallBackAsync;
_client.StartReceiving();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken stoppingToken)
{
// TODO: Unsubscribe from events
return Task.CompletedTask;
}
public static async void OnMessageHandlerAsync(object sender, MessageEventArgs e)
{
using var scope = _scopeFactory.CreateScope();
var handler = scope.ServiceProvider.GetRequiredService<MessageHandler>();
await handler.Handle(TODO: pass required args); // Move the logic to separate handler class to keep hosted service clean
}
...
}
I moved call to _client.StartReceiving(); after event subscription otherwise there is a chance for race condition when you receive event but you don't yet have subscribers and this event will be lost.
The second issue is as #PanagiotisKanavos said: async void can't be awaited, hence once your code hit first true async method (like DB access, http request, file read or any other I/O operation) the control is returned to the point where async void method was called and continues execution without waiting for operation completion. The whole app can even crash if you throw unhandled exception from such method, hence async void should be avoided. To prevent these problems wrap your async event handlers with sync methods that will block the execution with Wait() method:
public class TelegramHostedService : IHostedService
{
private IServiceScopeFactory _scopeFactory;
public TimedHostedService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_client = new TelegramBotClient(_token);
_client.OnMessage += OnMessageHandler;
_client.OnCallbackQuery += OnLoadCallBack;
_client.StartReceiving();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken stoppingToken)
{
// TODO: Unsubscribe from events
return Task.CompletedTask;
}
public static void OnMessageHandler(object sender, MessageEventArgs e)
{
OnMessageHandlerAsync(sender, e).Wait();
}
public static async Task OnMessageHandlerAsync(object sender, MessageEventArgs e)
{
using var scope = _scopeFactory.CreateScope();
var handler = scope.ServiceProvider.GetRequiredService<MessageHandler>();
await handler.Handle(TODO: pass required args); // Move the logic to separate handler class to keep hosted service clean
}
...
}

Registering a concrete type in Simple Injector and using it throws ActivationException

I am using Simple Injector to register a concrete type in the container in a .NET Core console app (in Program.cs), but Simple Injector throws an exception on start up:
The constructor of type Application contains the parameter with name 'configUpdater' and type ConfigUpdater, but ConfigUpdater is not registered. For ConfigUpdater to be resolved, it must be registered in the container. An implicit registration could not be made because Container.Options.ResolveUnregisteredConcreteTypes is set to 'false', which is now the default setting in v5. This disallows the container to construct this unregistered concrete type. For more information on why resolving unregistered concrete types is now disallowed by default, and what possible fixes you can apply, see https://simpleinjector.org/ructd
EDIT:
Adding a MRE example which throws the exception:
using System.Threading.Tasks;
using NLog;
using SimpleInjector;
namespace MRE
{
public static class Program
{
private static Container container;
static Program()
{
container = new Container();
container.Register<IApplication, Application>(Lifestyle.Singleton);
var appSettings = new AppSettings();
container.Register(
typeof(AppSettings),
() => appSettings,
Lifestyle.Singleton
);
container.RegisterConditional(
typeof(ILog),
typeCtx => typeof(NLogProxy<>).MakeGenericType(typeCtx.Consumer.ImplementationType),
Lifestyle.Singleton,
predCtx => true
);
container.Register<IConfigUpdater, ConfigUpdater>(Lifestyle.Scoped);
}
public static void Main(string[] args)
{
var application = container.GetInstance<IApplication>();
application.RunAsync();
}
}
public class AppSettings
{
public string ConnectionString { get; set; } = "DataSource=data.db";
}
public interface ILog
{
void Info(string message);
}
public class NLogProxy<T> : ILog
{
private static readonly NLog.ILogger Logger = LogManager.GetLogger(typeof(T).FullName);
public void Info(string message) => Logger.Log(LogLevel.Info, message);
}
public interface IApplication
{
Task RunAsync();
}
public class Application : IApplication
{
private readonly ILog logger;
private readonly IConfigUpdater configUpdater;
public Application(
ILog logger,
IConfigUpdater configUpdater
)
{
this.logger = logger;
this.configUpdater = configUpdater;
}
public Task RunAsync()
{
logger.Info("Running");
configUpdater.DoTask();
return Task.CompletedTask;
}
}
public interface IConfigUpdater
{
Task DoTask();
}
public class ConfigUpdater : IConfigUpdater
{
private readonly AppSettings appSettings;
private readonly ILog logger;
public ConfigUpdater(
AppSettings appSettings,
ILog logger
)
{
this.appSettings = appSettings;
this.logger = logger;
}
public Task DoTask()
{
var connectionString = appSettings.ConnectionString;
logger.Info(connectionString);
return Task.CompletedTask;
}
}
}
EDIT #2:
With the help of the MRE, I discovered my issue was actually hiding behind the scenes. It was a issue with using Lifestyle.Scoped which for some reason was not the first exception thrown. Setting the default lifestyle to AsyncScopedLifestyle fixes it.
With the help of the MRE, I found that the actual error was to do with the default Lifestyle SimpleInjector was using. Adding the line:
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
fixes the issue of this question.
As to why the Lifestyle exception wasn't thrown first, I don't know.

Dependency Injection in Worker Service to classes other than Controllers by IOptions

I know this is a repeated question , went through answers and dont know whats happening here. In this problem we need to transfer the values from appsettings.json to another class other than Controllers here its ServiceSettings.cs.
This is a sample 'hello world' like program, here we need transfer values from appsettings.json to plugins.
This is folder architecture
appsettings.json
"Application": {
"TimerInterval": 10000,
"LogLevel": "Debug"
}
I created a class based upon this app setting in class library-
ApplicationSettings.cs
public class ApplicationSettings
{
public int TimerInterval { get; set; }
public string LogLevel { get; set; }
}
I tried push data from appsettings via the last line code
services.Configure<ApplicationSettings>(hostContext.Configuration.GetSection("Application"));
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<IConfiguration>(hostContext.Configuration);
// Service Settings Injected here
services.AddOptions<ServiceSettings>();
services.AddHostedService<Worker>();
services.Configure<ApplicationSettings>(hostContext.Configuration.GetSection("Application"));
// for configure application
});
}
Here during start method of the worker class i need to get values from ServiceSettings() which always returns null value.
Worker.cs(Re Edited)
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IConfiguration _configuration;
private ServiceSettings _settings;
public Worker(ILogger<Worker> logger, IConfiguration config)
{
_logger = logger;
_configuration = config;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Start Asynch Method");
// Load Settings From Configuration Files
_settings = new ServiceSettings();
_settings.Load();
_logger.LogInformation("Settings: {setting}", _settings.TimerInterval);
return base.StartAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var values = _configuration.GetSection("DataSources").Get<List<DataSource>>();
while (!stoppingToken.IsCancellationRequested) {
await Task.Delay(Convert.ToInt32(_configuration["Application:TimerInterval"]), stoppingToken);
}
}
}
The service settings values are provided below which receives the null value
ServiceSettings.cs
public class ServiceSettings
{
private readonly IOptions<ApplicationSettings> _appSettings;
public ServiceSettings(IOptions<ApplicationSettings> appSettings)
{
_appSettings = appSettings;
}
public int TimerInterval { get; set; }
public string LogLevel { get; set; }
public void Load()
{
// Error is shown here
try { TimerInterval = Convert.ToInt32(_appSettings.Value.TimerInterval); }
catch { TimerInterval = 60; }
try
// Here too
{ LogLevel = Convert.ToString(_appSettings.Value.LogLevel).ToLower(); }
catch { LogLevel = "info"; }
}
}
I am pretty new to worker service, What i miss here? kindly guide me with the resources Thank you all.
This appears to be a design issue.
First lets fix the composition root. Avoid injecting IConfiguration. It can be seen as a code smell as IConfiguration should ideally be used in startup.
public class Program {
public static void Main(string[] args) {
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) => {
IConfiguration config = hostContext.Configuration;
// parse settings
ApplicationSettings appSettings = config
.GetSection("Application").Get<ApplicationSettings>();
//set defaults.
if(appSettings.TimerInterval == 0)
appSettings.TimerInterval = 60;
if(string.IsNullOrWhiteSpace(appSettings.LogLevel))
appSettings.LogLevel = "Debug";
services.AddSingleton(appSettings); //<-- register settings run-time data
services.AddHostedService<Worker>();
});
}
Note how the settings are extracted from configuration and added to the service collection.
Since there is already a strongly defined type (ApplicationSettings) There really is no need for the ServiceSettings based on what was shown in the original question.
Update the worker to explicitly depend on the actual object required.
public class Worker : BackgroundService {
private readonly ILogger<Worker> _logger;
private readonly ApplicationSettings settings;
public Worker(ILogger<Worker> logger, ApplicationSettings settings) {
_logger = logger;
this.settings = settings; //<-- settings injected.
}
public override Task StartAsync(CancellationToken cancellationToken) {
Console.WriteLine("Start Asynch Method");
_logger.LogInformation("Settings: {setting}", settings.TimerInterval);
return base.StartAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
while (!stoppingToken.IsCancellationRequested) {
await Task.Delay(settings.TimerInterval), stoppingToken);
}
}
}
You always have to get instances from your service collection. You typically do this by injecting them in class constructor.
// WRONG
// Worker.cs
_settings = new ServiceSettings();
This code does not compile because your ServiceSettings class has a constructor that requires one parameter but no parameter is given.
how should your class know anything about the options stored in service collection without any reference?
Then it seems to make no sense to have two classes with the same data ServiceSettings and ApplicationSettings are the same. If you need the application settings in a service inject IOptions<ApplicationSettings> that's all. If you need separate settings classes, provide them as IOption<MyOtherSectionSettings>.
In the end, it could look like so:
public class Worker {
private readonly ApplicationSettings _settings;
private readonly ILogger<Worker> _logger;
public Worker(IOptions<ApplicationSettings> settingsAccessor, ILogger<Worker> logger) {
_settings = settingsAccessor.Value;
_logger = logger;
}
public override Task StartAsync(CancellationToken cancellationToken) {
Console.WriteLine("Start Asynch Method");
_logger.LogInformation("Settings: {setting}", _settings.TimerInterval);
return base.StartAsync(cancellationToken);
}
}
Note that reading settingsAccessor.Value is the place where the framework really tries to access the configuration and so here we should think about error conditions (if not validated before).

Cannot access a disposed object in Task.Run

I am using .NET Core 3.1. I want to run some background processing without user having to wait for it to finish (it takes about 1 minute). Therefore, I used Task.Run like this:
public class MyController : Controller
{
private readonly IMyService _myService;
public MyController(IMyService myService)
{
_myService = myService;
}
public async Task<IActionResult> Create(...)
{
await _myService.CreatePostAsync(...);
return View();
}
}
public class MyService : IMyService
{
private readonly MyDbContext _dbContext;
private readonly IServiceScopeFactory _scopeFactory;
public MyService(MyDbContext dbContext, IServiceScopeFactory scopeFactory)
{
_dbContext = dbContext;
_scopeFactory = scopeFactory;
}
public async Task CreatePostAsync(Post post)
{
...
string username = GetUsername();
DbContextOptions<MyDbContext> dbOptions = GetDbOptions();
Task.Run(() => SaveFiles(username, dbOptions, _scopeFactory));
}
private void SaveFiles(string username, DbContextOptions<MyDbContext> dbOptions, IServiceScopeFactory scopeFactory)
{
using (var scope = scopeFactory.CreateScope())
{
var otherService = scope.ServiceProvider.GetRequiredService<IOtherService>();
var cntxt = new MyDbContext(dbOptions, username);
Post post = new Post("abc", username);
cntxt.Post.Add(post); <----- EXCEPTION
cntxt.SaveChanges();
}
}
}
I recieve the following exception in marked line:
System.ObjectDisposedException: 'Cannot access a disposed object. Object name: 'IServiceProvider'.'
Why does this happen? I used custom constructor (and not scope.ServiceProvider.GetRequiredService<MyDbContext>()) for MyDbContext because I need to save one additional propery (username) for later use in overriden methods.
public partial class MyDbContext
{
private string _username;
private readonly DbContextOptions<MyDbContext> _options;
public DbContextOptions<MyDbContext> DbOptions { get { return _options; } }
public MyDbContext(DbContextOptions<MyDbContext> options, string username) : base(options)
{
_username = username;
_options = options;
}
... other overriden methods
}
What am I doing wrong?
First of all, don't hide a thread-pool operation away in your service; let the calling coded decide whether to run the operation on the thread-pool or not:
As you are using dependency injection, the framework is disposing your DbContext at the end of the HTTP request.
You need to inject your service scope factory into your controller, and request the service from there:
public class MyController : Controller
{
private readonly IMyService _myService;
private readonly IServiceScopeFactory _scopeFactory;
public MyController(IMyService myService, IServiceScopeFactory scopeFactory)
{
_myService = myService;
_scopeFactory = scopeFactory;
}
public async Task<IActionResult> Create(...)
{
HostingEnvironment.QueueBackgroundWorkItem(SaveInBackground);
return View();
}
private async Task SaveInBackground(CancellationToken ct)
{
using (var scope = scopeFactory.CreateScope())
{
var scopedService = scope.ServiceProvider.GetRequiredService<IMyService>();
await scopedService.CreatePostAsync(...);
}
}
}
HostingEnvironment.QueueBackgroundWorkItem works in a similar way to Task.Run, except it ensures that the app doesn't shut down until all background work items have completed.
Your service would need to be something like this:
public class MyService : IMyService
{
private readonly MyDbContext _dbContext;
public MyService(MyDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task CreatePostAsync(Post post)
{
_dbContext.Post.Add(post);
await _dbContext.SaveChangesAsync();
}
}
UPDATE
To pass additional parameters to SaveInBackground:
private async Task SaveInBackground(YourParam param)
Then call like:
HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => SaveInBackground(yourParam));
You shoud create a Service with a Singleton lifecycle and inject a DBContext inside and queue all tasks inside

C# Dependency Injection : Injecting multiple interfaces into other services

I'd like to inject a number of interfaces to another service.
Let's take a look at 2 services that I want to have their dependency injected.
Inside Term.cs
private readonly IWSConfig WSConfig;
private readonly IMemoryCache MemCache;
public Term(IWSConfig wsConfig, IMemoryCache memoryCache)
{
WSConfig = wsConfig;
MemCache = memoryCache;
}
public async Task LoadData()
{
List<ConfigTerm> configTerm = await WSConfig.GetData(); // This is a web service call
...
}
Inside Person.cs
private readonly PersonRepo PersonRepository;
private readonly IMemoryCache MemCache;
private readonly ITerm Term;
private readonly IWSLoadLeave LoadLeave;
private readonly IWSLoadPartics LoadPartics;
public Person(PersonRepo personRepository, IMemoryCache memCache, ITerm term, IWSLoadLeave loadLeave, IWSLoadPartics loadPartics)
{
PersonRepository = personRepository;
MemCache = memCache;
Term = term;
LoadLeave = loadLeave;
LoadPartics = loadPartics;
}
Code in Startup.cs
services.AddDbContext<DBContext>(opts => opts.UseOracle(RegistryReader.GetRegistryValue(RegHive.HKEY_LOCAL_MACHINE, Configuration["AppSettings:RegPath"], "DB.ConnectionString", RegWindowsBit.Win64)));
services.AddTransient<ILogging<ServiceLog>, ServiceLogRepo>();
services.AddSingleton<IMemoryCache, MemoryCache>();
services.AddHttpClient<IWSConfig, WSConfig>();
services.AddHttpClient<IWSLoadLeave, WSLoadLeave>();
services.AddHttpClient<IWSLoadPartics, WSLoadPartics>();
var optionsBuilder = new DbContextOptionsBuilder<DBContext>(); // Can we omit this one and just use the one in AddDbContext?
optionsBuilder.UseOracle(RegistryReader.GetRegistryValue(RegHive.HKEY_LOCAL_MACHINE, Configuration["AppSettings:RegPath"], "DB.ConnectionString", RegWindowsBit.Win64));
services.AddSingleton<ITerm, Term>((ctx) => {
WSConfig wsConfig = new WSConfig(new System.Net.Http.HttpClient(), new ServiceLogRepo(new DBContext(optionsBuilder.Options))); // Can we change this to the IWSConfig and the ILogging<ServiceLog>
IMemoryCache memoryCache = ctx.GetService<IMemoryCache>();
return new Term(wsConfig, memoryCache);
});
services.AddSingleton<IPerson, Person>((ctx) => {
PersonRepo personRepo = new PersonRepo(new DBContext(optionsBuilder.Options)); // Can we change this?
IMemoryCache memoryCache = ctx.GetService<IMemoryCache>();
ITerm term = ctx.GetService<ITerm>();
WSLoadLeave loadLeave = new WSLoadLeave(new System.Net.Http.HttpClient(), new ServiceLogRepo(new DBContext(optionsBuilder.Options))); // Can we change this?
WSLoadPartics loadPartics = new WSLoadPartics(new System.Net.Http.HttpClient(), new ServiceLogRepo(new DBContext(optionsBuilder.Options))); // Can we change this?
return new Person(personRepo, memoryCache, term, loadLeave, loadPartics);
});
But there are some duplication here and there. I've marked as the comments in the code above.
How to correct it ?
[UPDATE 1]:
If I change the declaration from singleton with the following:
services.AddScoped<ITerm, Term>();
services.AddScoped<IPerson, Person>();
I'm getting the following error when trying to insert a record using the DbContext.
{System.ObjectDisposedException: Cannot access a disposed object. A
common cause of this error is disposing a context that was resolved
from dependency injection and then later trying to use the same
context instance elsewhere in your application. This may occur if you
are calling Dispose() on the context, or wrapping the context in a
using statement. If you are using dependency injection, you should let
the dependency injection container take care of disposing context
instances. Object name: 'DBContext'.
In my WSConfig, it will inherit a base class. This base class also have reference to the ServiceLogRepo, which will call the DbContext to insert a record to the database
In WSConfig
public class WSConfig : WSBase, IWSConfig
{
private HttpClient WSHttpClient;
public WSConfig(HttpClient httpClient, ILogging<ServiceLog> serviceLog) : base(serviceLog)
{
WSHttpClient = httpClient;
//...
}
//...
}
The WSBase class:
public class WSBase : WSCall
{
private readonly ILogging<ServiceLog> ServiceLog;
public WSBase(ILogging<ServiceLog> serviceLog) : base(serviceLog)
{
}
...
}
The WSCall class:
public class WSCall
{
private readonly ILogging<ServiceLog> ServiceLog;
public WSCall(ILogging<ServiceLog> serviceLog)
{
ServiceLog = serviceLog;
}
....
}
And the ServiceLogRepo code
public class ServiceLogRepo : ILogging<ServiceLog>
{
private readonly DBContext _context;
public ServiceLogRepo(DBContext context)
{
_context = context;
}
public async Task<bool> LogRequest(ServiceLog apiLogItem)
{
await _context.ServiceLogs.AddAsync(apiLogItem);
int i = await _context.SaveChangesAsync();
return await Task.Run(() => true);
}
}
I also have the following in Startup.cs to do the web service call upon application load.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, ITerm term)
{
....
System.Threading.Tasks.Task.Run(async () => await term.LoadData());
}
It seems when going into term.LoadData(), the DBContext is disposed already.
First properly register all the necessary dependencies in ConfigureServices using the appropriate liftetime scopes
services.AddDbContext<DBContext>(opts => opts.UseOracle(RegistryReader.GetRegistryValue(RegHive.HKEY_LOCAL_MACHINE, Configuration["AppSettings:RegPath"], "DB.ConnectionString", RegWindowsBit.Win64)));
services.AddTransient<ILogging<ServiceLog>, ServiceLogRepo>();
services.AddSingleton<IMemoryCache, MemoryCache>();
services.AddHttpClient<IWSConfig, WSConfig>();
services.AddHttpClient<IWSLoadLeave, WSLoadLeave>();
services.AddHttpClient<IWSLoadPartics, WSLoadPartics>();
services.AddScoped<ITerm, Term>();
services.AddScoped<IPerson, Person>();
Given the async nature of the method being called in Configure the DbContext is being disposed before you are done with it.
Now ideally given what you are trying to achieve you should be using a background service IHostedServive which will be started upon startup of the application.
public class TermHostedService : BackgroundService {
private readonly ILogger<TermHostedService> _logger;
public TermHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger) {
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
_logger.LogInformation("Term Hosted Service running.");
using (var scope = Services.CreateScope()) {
var term = scope.ServiceProvider.GetRequiredService<ITerm>();
await term.LoadData();
_logger.LogInformation("Data Loaded.");
}
}
public override async Task StopAsync(CancellationToken stoppingToken) {
_logger.LogInformation("Term Hosted Service is stopping.");
await Task.CompletedTask;
}
}
when registered at startup
services.AddHostedService<TermHostedService>();
Reference Background tasks with hosted services in ASP.NET Core

Categories