在ASP.NET Core中请求结束后使用(注入DI)DbContext [英] Use (DI injected) DbContext after request ended in ASP.NET Core

查看:97
本文介绍了在ASP.NET Core中请求结束后使用(注入DI)DbContext的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于我的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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆