在ASP.NET Core中请求结束后使用(注入DI)DbContext [英] Use (DI injected) DbContext after request ended in ASP.NET Core
问题描述
对于我的ASP.NET Core MVC应用程序,我需要一个处理数据的控制器动作.该处理需要一些时间,所以我不想阻止该请求.在控制器操作中,我宁愿启动后台工作者并立即终止请求,告诉用户处理正在进行中.然后使用第二个控制器操作来访问处理后的数据.
For my ASP.NET Core MVC application I need a controller action which processes data. This processing takes some time so I do not want to block the request. In the controller action I rather start a background worker and end the request immediately telling the user that the processing is in progress. A second controller action is then used to access the processed data.
在后台工作人员中,我需要访问 DbContext
以便将处理后的数据存储在数据库中.(或者通过依赖注入注入的任何其他服务.)我发现通过 IServiceScopeFactory
创建一个新的,与请求无关的作用域是有效的,这反过来又给了我一个 ServiceProvider
:
In the background worker I need to access the DbContext
for storing the processed data in my database. (Or any other service which was injected via Dependency Injection.) I found that creating a new, request-independent scope via an IServiceScopeFactory
works which in turn gives me a ServiceProvider
:
public class ProcessingController : Controller
{
private readonly IServiceScopeFactory mServiceProvider;
public HomeController(IServiceScopeFactory serviceProvider)
{
mServiceProvider = serviceProvider;
}
public IActionResult BeginProcessing(int id)
{
var longRunningScope = mServiceProvider.CreateScope();
var _ = Task.Run(() => {
try {
var context = longRunningScope.ServiceProvider.GetRequiredService<DbContext>();
var workItem = context.Items.First(i => i.Id == id)
...
}
finally {
longRunningScope.Dispose();
}
});
return Ok();
}
}
是否有更好的方法(更多ASP.NET-Core风格)?请注意,我的长期运行"操作仅需2-5秒,并且需要同时处理多个用户.不需要按顺序处理请求的后台线程.
Is there a better (more ASP.NET-Core-style) way to do this? Please note that my "long-running" action only takes 2-5 seconds and that multiple users need to be handled simultaneously. A background thread which processes requests in sequence is not wanted.
推荐答案
I determined that a hosted services is not well suited for my needs because it needs to be implemented explicitly and injected, making it hard to pass data to it.
这是一种更灵活,更易于使用的解决方案 ScopedBackgroundTaskRunner
,该解决方案在侦听关闭事件的同时,在自己的任务和自己的范围内运行操作.该操作会接收到相应的取消令牌以及一个限定范围的 ServiceProvider
以获得所需的服务.
Here is a more flexible and easier to use solution ScopedBackgroundTaskRunner
, which runs an action in its own task and in its own scope while listening to the shutdown event. The action receives the corresponding cancellation token as well as a scoped ServiceProvider
to obtain any needed services.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace WebApplication2.Support
{
/// <summary>
/// Service class that executes tasks which run in their own thread with their own scope
/// and can thus continue executing after the web request ended.
/// </summary>
/// <remarks>
/// Register via:
/// services.AddTransient<ScopedBackgroundTaskRunner>();
/// </remarks>
public class ScopedBackgroundTaskRunner
{
private readonly ILogger<ScopedBackgroundTaskRunner> mLogger;
private CancellationTokenSource mStoppingCts;
private IServiceProvider mServiceProvider;
public ScopedBackgroundTaskRunner(IServiceProvider services,
ILogger<ScopedBackgroundTaskRunner> logger,
IHostApplicationLifetime lifetime)
{
mServiceProvider = services;
mLogger = logger;
lifetime.ApplicationStopping.Register(OnAppStopping);
}
private void OnAppStopping()
{
if (mStoppingCts != null)
{
mLogger.LogDebug($"Cancel due to app shutdown");
mStoppingCts.Cancel();
}
}
public void Execute(Action<IServiceProvider, CancellationToken> action, CancellationToken stoppingToken) {
Execute(action, "<unnamed>", stoppingToken);
}
public void Execute(Action<IServiceProvider, CancellationToken> action, string actionName, CancellationToken stoppingToken)
{
mStoppingCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
var scope = mServiceProvider.CreateScope();
var _ = Task.Run(() => {
mLogger.LogTrace($"Executing action '{actionName}' on thread {Thread.CurrentThread.ManagedThreadId}...");
try
{
action.Invoke(scope.ServiceProvider, mStoppingCts.Token);
}
finally {
mLogger.LogTrace($"Action '{actionName}' {(mStoppingCts.IsCancellationRequested ? "canceled" : "finished")}" +
$" on thread {Thread.CurrentThread.ManagedThreadId}");
scope.Dispose();
var mStoppingCtsCopy = mStoppingCts;
mStoppingCts = null;
mStoppingCtsCopy.Dispose();
}
}, mStoppingCts.Token);
}
}
}
这篇关于在ASP.NET Core中请求结束后使用(注入DI)DbContext的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!