I would like to implement a recurring (timed) IHostedService instance in ASPNET Core that can be stopped and started on demand. My understanding is that IHostedService(s) are started by the framework on application startup.
However, I would like to be able to start/stop the service 'manually', perhaps using an on/off toggle via a UI. Ideally the "off" state would dispose of currently running service, and the "on" state would then create a new instance.
I've read the MS docs here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1.
My initial thought was to get an instance of the running service and then call the public StopAsync(CancellationToken token) method. However I'm a little stuck when it comes to which token I should pass in, and the same could be said for the StartAsync(CancellationToken cancellationToken) method.
Any ideas on how this should be done, or if it's even advisable? Is my approach somehow going against the intended design of hosted services in ASPNET Core?
EDIT 7.27.2018
So it appears after some more research (aka actually reading the documentation :D) that hosted services StartAsync/StopAsync methods are indeed meant to coincide with the lifetime of the application. Registered IHostedServices seem to not be added to the DI container for injection into other classes.
Therefore I do not think my initial idea will work. For now I registered my services with configuration dependencies (IOptions<T>) that can be updated at runtime. As the hosted services is processing, it will check the configuration to see if it should continue, otherwise it will just wait (instead of stopping or disposing of the hosted service).
I'll probably mark this as my answer soon, unless I hear of some other ideas.
For StopAsync(CancellationToken token), you could pass new System.Threading.CancellationToken(). In the defination of public CancellationToken(bool canceled), canceled indicates state for the token. For your scenario, there is no need to specify the canceled since you want to Stop the service.
You could follow below step by step:
Create IHostedService
public class RecureHostedService : IHostedService, IDisposable
{
private readonly ILogger _log;
private Timer _timer;
public RecureHostedService(ILogger<RecureHostedService> log)
{
_log = log;
}
public void Dispose()
{
_timer.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_log.LogInformation("RecureHostedService is Starting");
_timer = new Timer(DoWork,null,TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_log.LogInformation("RecureHostedService is Stopping");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private void DoWork(object state)
{
_log.LogInformation("Timed Background Service is working.");
}
}
Register IHostedService
services.AddSingleton<IHostedService, RecureHostedService>();
Start and Stop Service
public class HomeController : Controller {
private readonly RecureHostedService _recureHostedService;
public HomeController(IHostedService hostedService)
{
_recureHostedService = hostedService as RecureHostedService;
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
_recureHostedService.StopAsync(new System.Threading.CancellationToken());
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
_recureHostedService.StartAsync(new System.Threading.CancellationToken());
return View();
} }
Using Blazor Server, you can start and stop background services in the following ways. Asp.net Core MVC or Razor is the same principle
First, implement an IHostService
public class BackService : IHostedService, IDisposable
{
private readonly ILogger _log;
private Timer _timer;
public bool isRunning { get; set; }
public BackService(ILogger<V2rayFlowBackService> log)
{
_log = log;
}
public void Dispose()
{
_timer.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_log.LogInformation($"begin {DateTime.Now}");
_timer = new Timer(DoWorkAsync, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
isRunning = false;
_log.LogInformation($"{DateTime.Now} BackService is Stopping");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private void DoWorkAsync(object state)
{
_log.LogInformation($"Timed Background Service is working. {DateTime.Now}");
try
{
isRunning = true;
// dosometing you want
}
catch (Exception ex)
{
isRunning = false;
_log.LogInformation("Error {0}", ex.Message);
throw ex;
}
}
}
Registration Service In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<BackService>();
services.AddHostedService(sp => sp.GetRequiredService<BackService>());
}
Inject background services into Blazor components
public class IndexBase:ComponentBase
{
[Inject]
BackService BackService { set; get; }
protected override void OnInitialized()
{
if (BackService.isRunning)
{
BackService.StopAsync(new System.Threading.CancellationToken());
}
base.OnInitialized();
}
public void on()
{
if (!BackService.isRunning)
{
BackService.StartAsync(new System.Threading.CancellationToken());
}
}
public void off()
{
if (BackService.isRunning)
{
BackService.StopAsync(new System.Threading.CancellationToken());
}
}
}
#page "/"
#inherits IndexBase
<h1>Hello, world!</h1>
Welcome to your new app.
<button #onclick="on">Start</button>
<button #onclick="off">Stop</button>
reference
You should inherit your HostedService from your own Interface and register your service as Singleton, but in a difference way:
First register your service with AddHostedService generic method.
services.AddHostedService<TimerHostedService>();
Then add a public static field to your class named Instance, that holds the instance reference of your class and set its value in the constructor!
Then put a factory in the ConfigureServices for registering your service as singleton that returns the static instance field !
Here is the sample code:
(in your HostedService.cs: )
public interface ITimerHostedService : IHostedService
{
}
public class TimerHostedService : ITimerHostedService
{
private static TimerHostedService _instance;
public static TimerHostedService Instance => _instance;
public TimerHostedService(ILogger<TimerHostedService> logger)
{
if(_instance == null)
{
_instance = this;
}
}
}
And here is the code for registering your service as singleton ( in Startup.cs ):
services.AddHostedService<TimerHostedService>();
services.AddSingleton<ITimerHostedService, TimerHostedService>(serviceProvider =>
{
return TimerHostedService.Instance;
});
And here is the code in your Controller to manually start / stop your HostedService:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly ITimerHostedService _hostedService;
public HomeController(ILogger<HomeController> logger, ITimerHostedService hostedService)
{
_logger = logger;
_hostedService = hostedService;
}
public async Task<IActionResult> Start()
{
await _hostedService.StartAsync(default);
return Ok();
}
public async Task<IActionResult> Stop()
{
await _hostedService.StopAsync(default);
return Ok();
}
}
Happy Coding!
Enjoy Your Lovely Moments :X
Related
I'm having trouble with a Scoped context getting disposed before a controller route has finished executing...
This is example code that demonstrates the problem as I can't share the real code
I have a Unit of Work that would 'wrap' the repository layer
public interface IUnitOfWork
{
void Dispose();
bool GetContextDisposed();
}
public class UnitOfWork : IDisposable, IUnitOfWork
{
private ISomeContext _context;
public ISomeRepository someRepository;
public UnitOfWork(ISomeContext context)
{
_context = context;
someRepository = new SomeRepository(_context);
}
public void Dispose()
{
_context.Dispose();
}
public bool GetContextDisposed()
{
return _context.GetDisposed();
}
}
Context
public interface ISomeContext
{
void Dispose();
public bool GetDisposed();
}
public class SomeContext : IDisposable, ISomeContext
{
private bool isDisposed = false;
public void Dispose()
{
Console.WriteLine("Dispose being called on Context");
isDisposed = true;
}
public bool GetDisposed()
{
return isDisposed;
}
}
These are both registered as scoped as I want to get one per request to an endpoint
services.AddScoped<ISomeContext, SomeContext>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
Now in my controller I have a long running task that happens and after it is finished I need to do some cleanup but I want to go ahead and respond to the request so it doesn't have to wait for these long running tasks to complete
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IUnitOfWork _uow;
public ValuesController(IUnitOfWork uow)
{
_uow = uow;
}
[Route("Test")]
public async Task<IActionResult> GetTest()
{
Task myTask = SomeLongRunningThing();
try
{
return Ok();
}
finally
{
Response.OnCompleted(async () =>
{
Task continueTask = myTask.ContinueWith(async (task) =>
{
await Task.Delay(5000);
Console.WriteLine("Checking if context is disposed...");
Console.WriteLine(_uow.GetContextDisposed());
});
await myTask;
await continueTask;
});
}
Console.WriteLine("End of route");
}
private Task SomeLongRunningThing()
{
return Task.Run(() =>
{
Task.Delay(10000);
});
}
}
The output shows
Dispose being called on Context
Dispose being called on Context
Dispose being called on Context
Dispose being called on Context
Checking if context is disposed...
True
What could be causing the context to be disposed before the route is fully completed?
I've tried messing with the lifetimes but it doesn't seem any lifetime helps the situation.
Version Stuff
.net core 3.1 MVC
Running on Windows 10
Thanks
Figured out something that works for my use case, I think...
I inject IServiceProvider into my controller as well
private readonly IUnitOfWork _uow;
private readonly IServiceProvider _services;
public ValuesController(IUnitOfWork uow, IServiceProvider services)
{
_uow = uow;
_services = services;
}
Then during the Task.Run(() => {}) and ContinueWith(async (task) => {}) I can use it to create a new scope and get an IUnitOfWork
private Task SomeLongRunningThing(OperationContext context)
{
return Task.Run(() =>
{
using (var scope = _services.CreateScope())
{
IUnitOfWork uow = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
OperationContext.Current = context;
Task.Delay(10000);
}
});
}
Task continueTask = myTask.ContinueWith(async (task) =>
{
using(var scope = _services.CreateScope())
{
IUnitOfWork uow = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
await Task.Delay(5000);
Console.WriteLine("Checking if context is disposed...");
Console.WriteLine(uow.GetContextDisposed());
}
});
A user can trigger a long-running job by sending a request to an ASP.NET Core controller. Currently, the controller executes the job and then sends a 200 OK response. The problem is that the client has to wait rather long for the response.
This is why I am currently trying to process the job in a background task. I am using an IBackgroundTaskQueue where all jobs are stored and an IHostedService that processes the jobs whenever a new one is enqueued. It is similar to the code in the Microsoft documentation.
But the job does need access to the database and therefore the user has to authenticate using Active Directory. Hence, I need access to the HttpContext.User property in the background task. Unfortunately, the HttpContext is disposed when the response is sent and before the processing of the job begins.
Demonstration
public class Job
{
public Job(string message)
{
Message = message;
}
public string Message { get; }
}
The controller enqueues a new job in the task queue.
[HttpGet]
public IActionResult EnqueueJob()
{
var job = new Job("Hello World");
this.taskQueue.QueueBackgroundWorkItem(job);
return Accepted();
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Job> jobs = new ConcurrentQueue<Job>();
private SemaphoreSlim signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(Job job)
{
jobs.Enqueue(job);
signal.Release();
}
public async Task<Job> DequeueAsync(CancellationToken cancellationToken)
{
await signal.WaitAsync(cancellationToken);
jobs.TryDequeue(out var job);
return job;
}
}
The IHostedService creates a new JobRunner for each job it dequeues. I'm using a IServiceScopeFactory here to have dependency injection available. JobRunner also has a lot more dependencies in the real code.
public class JobRunnerService : BackgroundService
{
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly IBackgroundTaskQueue taskQueue;
public JobRunnerService(IServiceScopeFactory serviceScopeFactory, IBackgroundTaskQueue taskQueue)
{
this.serviceScopeFactory = serviceScopeFactory;
this.taskQueue = taskQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (stoppingToken.IsCancellationRequested == false)
{
var job = await taskQueue.DequeueAsync(stoppingToken);
using (var scope = serviceScopeFactory.CreateScope())
{
var serviceProvider = scope.ServiceProvider;
var runner = serviceProvider.GetRequiredService<JobRunner>();
runner.Run(job);
}
}
}
}
public class JobRunner
{
private readonly ILogger<JobRunner> logger;
private readonly IIdentityProvider identityProvider;
public JobRunner(ILogger<JobRunner> logger, IIdentityProvider identityProvider)
{
this.logger = logger;
this.identityProvider= identityProvider;
}
public void Run(Job job)
{
var principal = identityProvider.GetUserName();
logger.LogInformation($"{principal} started a new job. Message: {job.Message}");
}
}
public class IdentityProvider : IIdentityProvider
{
private readonly IHttpContextAccessor httpContextAccessor;
public IdentityProvider(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetUserName()
=> httpContextAccessor.HttpContext.User.Identity.Name; // throws NullReferenceException
}
Now, when sending a request, a NullReferenceException is thrown in JobRunner.Run() because httpContextAccessor.HttpContext is null.
What I've tried
I haven't had a good idea yet how to approach this problem. I know that it would be possible to copy the necessary information from the HttpContext, but don't know how to make them available to dependency injection services.
I thought that maybe I could create a new IServiceProvider that uses the services of an old one, but replaces the implementation for IHttpContextAccesor, but it does not seem to be possible.
How can I use the HttpContext in the background task although the response has been completed?
I have implemented a REST API Server with asp.NET Core. I need a background task which does some administrative stuff.
I tried the several suggestions in the web to create a timed background task and implemented it as fpllowing:
BackgroundService.cs
public abstract class BackgroundService : IHostedService, IDisposable
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
_executingTask = ExecuteAsync(_stoppingCts.Token);
if (_executingTask.IsCompleted)
{
return _executingTask;
}
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
if (_executingTask == null)
{
return;
}
try
{
_stoppingCts.Cancel();
}
finally
{
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
RecureHostedService.cs
public class RecureHostedService : BackgroundService
{
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private int _executionCount = 0;
private Timer _timer;
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var count = Interlocked.Increment(ref _executionCount);
log.InfoFormat(
"Timed Hosted Service is working. Count: {0}", count);
await Task.Delay(10000, stoppingToken);
}
}
}
SystemController.cs
public class SystemController : Controller
{
private readonly RecureHostedService _recureHostedService;
public SystemController(IHostedService hostedService)
{
_recureHostedService = hostedService as RecureHostedService;
}
[HttpGet(ApiRoutes.System.Start)]
public IActionResult Start()
{
Console.WriteLine("Start Service");
_recureHostedService.StartAsync(new CancellationToken());
return Ok();
}
[HttpGet(ApiRoutes.System.Stop)]
public IActionResult Stop()
{
Console.WriteLine("Stop Service");
Console.WriteLine(_recureHostedService == null);
_recureHostedService.StopAsync(new CancellationToken());
return Ok();
}
}
in Startup.cs I added the Service as Singleton:
services.AddSingleton<IHostedService,RecureHostedService>();
The RecureHostedService starts on startup of my app and does its work. When I do an APIrequest /api/v1/stop from the SystemController (method Stop()) I got an NullReferenceException in
_recureHostedService.StopAsync(new CancellationToken());
I found out that the private member variable _recureHostedService is null even if I had assigned it in the Constructor of the SystemController. So it seems that the Service Injection of _recureHostedService is not working. So the line
_recureHostedService = hostedService as RecureHostedService;
in the Constructor of SystemController assigns null to _recureHostedService.
How can that be? I've a lot of other Services injected to other Controllers and all works fine.
Any idea from anyone, why it is not working with that Hosted Service?
Best
Mic
I used a set of static variables for myself
public class RecureHostedService : BackgroundService {
public static bool isPause = false;
/* for example
public static string ErrorText;
public static bool isError = false;
public static bool isWorked = false;
public static bool firstStart = true; */
protected async override Task ExecuteAsync(CancellationToken stoppingToken){
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
if (isPause)
continue;
var count = Interlocked.Increment(ref _executionCount);
log.InfoFormat(
"Timed Hosted Service is working. Count: {0}", count);
await Task.Delay(10000, stoppingToken);
}
}
}
public class SystemController : Controller
{
[HttpGet(ApiRoutes.System.Start)]
public IActionResult Start()
{
Console.WriteLine("Start Service");
RecureHostedService.isPause = false;
return Ok();
}
[HttpGet(ApiRoutes.System.Stop)]
public IActionResult Stop()
{
Console.WriteLine("Stop Service");
RecureHostedService.isPause = true;
return Ok();
}
}
You need to register you hosted service using AddHostedService instedad of AddSingleton
so it will be something like this
services.AddHostedService<IHostedService,RecureHostedService>();
If you perfer inject IHostedService on constructor, keep _recureHostedService property declared as type of IHostedService, instead of the concrete class RecureHostedService. It's not necessary down-casting here.
[Route("[controller]")]
public class SystemController : Controller
{
private readonly IHostedService _recureHostedService;
public SystemController(IHostedService hostedService)
{
_recureHostedService = hostedService ?? throw new ArgumentNullException(nameof(hostedService));
}
}
Update
I've tested this controller, no null exception any longer in either this way
services.AddSingleton<IHostedService, RecureHostedService>();
or
services.AddHostedService(p =>
{
return new RecureHostedService();
});
Using dependency injection to obtain an IHostedService will inject whichever hosted service was defined first. Which is likely to be the web server itself. Instead you should define two service definitions that return the same singleton;
services.AddSingleton<RecureHostedService>();
services.AddSingleton<IHostedService>(p => p.GetRequiredService<RecureHostedService>());
public class SystemController : Controller
{
private readonly RecureHostedService _recureHostedService;
public SystemController(RecureHostedService hostedService)
{
_recureHostedService = hostedService;
}
}
I'm trying to make call to a function every specified interval of time, for that m using Background service, here is what I have done:
Here is the Alerting controller where I have the function:
public class AlertingController : ControllerBase
{
private readonly DatabaseContext _context;
private readonly IMapper _mapper;
public AlertingController(DatabaseContext context, IMapper mapper)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
public AlertingController()
{
}
//function that adds in the DB
public async Task<AlertingResponse> GetAlertingToDB()
{
AlertingResponse dataGet;
using (var httpClient = new HttpClient())
{
using (var response = await httpClient
.GetAsync(MetricApiLink))
{
string apiResponse = await response.Content.ReadAsStringAsync();
dataGet = JsonConvert.DeserializeObject<AlertingResponse>(apiResponse);
}
}
if (dataGet.data.alerts != null || dataGet.data.alerts.Count > 0)
{
foreach (var alert in dataGet.data.alerts)
{
CreateAlertQuery QueryAlert = new CreateAlertQuery();
QueryAlert.Name = alert.labels.alertname;
QueryAlert.Instance = alert.labels.instance;
QueryAlert.Serverity = alert.labels.severity;
QueryAlert.Summary = alert.annotations.summary;
QueryAlert.State = alert.state;
QueryAlert.ActiveAt = alert.activeAt;
var _Alert = _mapper.Map<AlertingDataModel>(QueryAlert);
_context.Alertings.Add(_Alert);
await _context.SaveChangesAsync();
}
}
return null;
}
}
I have tested the method with a HTTPGET request, it works fine, add the alerts into my database:
I have created a scooped service where I called the function GetAlertingToDB():
internal interface IScopedAlertingService
{
Task DoWork(CancellationToken stoppingToken);
}
public class ScopedAlertingService : IScopedAlertingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedAlertingService(ILogger<ScopedAlertingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
AlertingController _AlertingToDB = new AlertingController();
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await _AlertingToDB.GetAlertingToDB();
await Task.Delay(10000, stoppingToken);
}
}
}
I have also created the Class that will consume my service, and will run in the BackGround:
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedAlertingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await Task.CompletedTask;
}
}
I injected the dependencies on the Startup Class and added the hosted service:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedAlertingService, ScopedAlertingService>();
The functions are working just fine untill a call the GetAlertingToDB() function and it doesn't work.
Any help would be great, thanks everyone :)
Personally I would rearrange your solution so that your background service doesn't need to create a Controller. Instead the controller, if you still need it at all, should call into your ScopedAlertingService where the work is performed once. Your background service can simply loop forever, with an await Task.Delay().
public class ScopedAlertingService : IScopedAlertingService
{
public async Task DoWork(CancellationToken stoppingToken)
{
// move contents of your AlertingController.GetAlertingToDB here
}
}
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly IServiceProvider _services;
public ConsumeScopedServiceHostedService(IServiceProvider services)
{
_services = services;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(10000, stoppingToken);
using (var scope = _services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedAlertingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
}
}
Hangfire RecurringJob would be an option for you case. you can check it here https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html.
The benefit of using it is: you have a dashboard to check when the task will be fired and the result of the task.
There are several options for doing this.
Please read the following link from the Microsoft Documentation which has several examples on how to do this in .NET Core and ASP.NET Core:
Worker Service In NET Core
It is called Worker Services.
You basically implement two interfaces: IHostedService, IDisposable
Then you register your service inside you Startup class in your ConfigureServices method like this:
services.AddHostedService<MyCoolWorkerServiceClass>();
For a Complete Example
One last sugestion. The example uses System.Threading.Timer... But I think it is better to use a System.Timers.Timer with AutoReset = false.
The reason is to avoid overlapping runs of your service. Once a run is done then you start your timer again.
But then again it all depends on what you want to achieve.
I have a Controller, which has 2 functions and a middleware.
My middleware
try
{
var request = httpContext.Request;
if (/* some logic here */)
{
await _next(httpContext);
// Some logic here
var someReturnValue = someFunction();
// Need to call the Controller Method here
// call SaveStaging(someReturnValue);
}
else
{
await _next(httpContext);
}
}
catch (Exception ex)
{
await _next(httpContext);
}
In my Controller
[Authorize(AuthenticationSchemes = "APIKeyAuthentication")]
[LogAction]
[ApiController]
[Route("api/[controller]/[action]")]
public class MyController : ControllerBase
{
private IHttpContextAccessor _accessor;
public MyController(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
[HttpPost]
public async Task<ActionResult<ServiceResult>> PostData([FromBody] DataObj request)
{
ServiceResult result = new ServiceResult();
result.isSuccessful = true;
return Ok(result);
}
public async Task SaveStaging(int logNumber)
{
}
}
So, initially, user will call the PostData endpoint. In this method, it will return that it accepts the data posted by user.
Then in the middleware, I will need to do some things, and finally, I would like to call the SaveStaging method in the Controller. I want this SaveStaging method to be only accessible for the middleware, and should not be called directly by the user.
So my question:
How to call this method from the middleware?
How to limit SaveStaging to be accessible only to the middleware?
You should consider wrapping SaveStaging into a service because it appears to be a cross-cutting concern. Beyond that, it's likely wise to implement a layered architecture as a matter of refactoring.
Since this is .NET Core, I would recommend fully embracing dependency injection and injecting the service into your middleware.
public interface IStagingService
{
Task Save(int logNumber);
}
public class MyController : ControllerBase
{
private readonly IStagingService _service;
private readonly IHttpContextAccessor _accessor;
public MyController(IStagingService service, IHttpContextAccessor accessor)
{
_service = service ?? throw new ArgumentNullException(nameof(service));
_accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));;
}
//....
}
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly IStagingService _service;
public RequestCultureMiddleware(RequestDelegate next, IStagingService service)
{
_next = next;
_service = service ?? throw new ArgumentNullException(nameof(service));
}
public async Task InvokeAsync(HttpContext context)
{
// ...
}
In the beginning, your service's will likely start out small and simple. In the event the app grows, the complexity of this layer will likely increase, spawning the need for some facade services (i.e. services composed of services) and maybe some providers for thing like data access.