I'm working with a ASP.NET Core 6 API project. I'm trying to implement a task scheduler.
The solution has different components like:
API project
Memory Queue (CL)
Worker Service
Task Service (CL)
Common Service (CL, used by API and TaskService)
API > Queue > Worker Service > Task Service > Common Service
Request first comes to the API, it saves a record to the DB and passes the id and task-type (fully qualified assembly name) to the memory queue. Worker service keep checking the queue in background, if there is any record, it will fetch it, and load the assembly by fully qualified name, and then execute the specific method of a Task service.
To perform some operation, task service calls common service. And the common service uses claims from Httpcontext when request comes from API. But when task service calls common service, it doesn't have Httpcontext.
Worker service:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var processingTasks = new List<Task>();
while (!stoppingToken.IsCancellationRequested)
{
var tasksToProcess = new List<TaskJobModel>();
// Dequeue a batch of tasks to process
while (_taskQueue.TryDequeue(out var task))
{
if (task != null)
tasksToProcess.Add(task);
}
if (tasksToProcess.Count > 0)
{
// Process tasks in parallel
foreach (var task in tasksToProcess)
{
var processingTask = ProcessTaskAsync(task);
processingTasks.Add(processingTask);
}
// Wait for all tasks to complete
await Task.WhenAll(processingTasks);
// Clear the list of processing tasks
processingTasks.Clear();
}
// Delay before the next iteration
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
}
await Task.WhenAll(processingTasks);
}
private Task ProcessTaskAsync(TaskJobModel taskJobModel)
{
try
{
if (string.IsNullOrEmpty(taskJobModel.JobType))
return Task.CompletedTask;
Assembly.Load(taskJobModel.JobType);
var type = Type.GetType(taskJobModel.JobType) ??
AppDomain.CurrentDomain.GetAssemblies()
.Select(a => a.GetType(taskJobModel.JobType))
.FirstOrDefault(t => t != null);
if (type == null)
throw new Exception($"({taskJobModel.JobType}) cannot by instantiated");
object? instance = GetInstance(type);
if (instance is not IWorkerService task)
return Task.CompletedTask;
//execute method from the specific type
task.ExecuteAsync(taskJobModel.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing task {taskId}", taskJobModel.Id);
}
return Task.CompletedTask;
}
private object GetInstance(Type type)
{
foreach (var constructor in type.GetConstructors())
{
var parameters = constructor.GetParameters().Select(parameter =>
{
var service = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService(parameter.ParameterType);
if (service == null)
throw new Exception("Unknown dependency");
return service;
});
//create an instance
return Activator.CreateInstance(type, parameters.ToArray()) ?? new { };
}
}
Problem statement
This works fine till now. However, Taskservice depends on some common services to perform some operation. But I could not find a way to register its dependencies.
I tried to register it into worker service startup.
.ConfigureServices(services =>
services.AddScoped<IConfigSetter, CustomServerConfig>();
services.AddScoped<IServerConfig, CustomServerConfig>();
services.AddHostedService<Worker>();
...
But it appears unregistered in the TaskService.
TaskService.cs
private readonly IServiceProvider _serviceProvider;
public TaskService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task ExecuteAsync(Guid taskId)
{
var config = _serviceProvider.GetService<IConfigSetter>();
}
Limitation
This cannot be registered under API startup, as I wanted to use CustomServerConfig instead of the actual one which is used by API project.
Creating a dependency to the service provider is a bad practice in Object oriented programming. Instead of actual dependency to a service, your code is free to resolve any dependency on its own.
But take a look at your code
What you do here is you have a dependency to a service provider (bad) but then you use the service provider to resolve the
IConfigSetter.Refactor it then to