DalSoft / DalSoft.Hosting.BackgroundQueue

Alternative solution to HostingEnvironment.QueueBackgroundWorkItem in .NET Core https://stackoverflow.com/questions/36945253/alternative-solution-to-hostingenvironment-queuebackgroundworkitem-in-net-core
MIT License
47 stars 5 forks source link

Handling Dependencies #3

Closed higginsddh closed 2 days ago

higginsddh commented 5 years ago

Thanks for providing this library, looks really helpful!

Was wondering, how have you handled injecting dependencies into the queued task?

At https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2#consuming-a-scoped-service-in-a-background-task, they show injecting IServiceProvider into the hosted service to create a scope and pull services from that but don't see a way to do it with this library.

DalSoft commented 5 years ago

I might get round to supporting this on registration, there is a open PR but it needs changing to support passing the IServiceProvider in the invoking delegate as well as on exceptions. This hasn't been a priority for me though because I think MS will include the functionality in this package in Core 3.0.

However because it is just a delegate you can pass IServiceProvider into the delegate, just remember to create a new scope rather than just get the service.

public EmailController(BackgroundQueue backgroundQueue, IServiceProvider  serviceProvider)
{
   _backgroundQueue = backgroundQueue;
   _serviceProvider = serviceProvider;
}

[HttpPost, Route("/")]
public IActionResult SendEmail([FromBody]emailRequest)
{
   _backgroundQueue.Enqueue(async cancellationToken =>
   {
       using (var scope = _serviceProvider.CreateScope())
        {
            var scopedProcessingService = scope.ServiceProvider.GetRequiredService<IScopedProcessingService>();

            scopedProcessingService.DoWork();
        }
   });

   return Ok();
}

Caveat I haven't tested this, but see no reason why this shouldn't work because IServiceProvider is a singleton.

alexishughes commented 5 years ago

Hi. I tried to get this going. I have an app that downloads big files in the background but needs to write to the database during this process for progress etc.

Here is Startup.cs

`public void ConfigureServices(IServiceCollection services) { services.Configure(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; });

        services.AddBackgroundQueue(onException: exception =>
        {
            Console.WriteLine(exception.Message);
            throw exception;
        });

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
        services.AddDbContext<Models.db_productpriceContext>();
        services.AddDefaultIdentity<IdentityUser>()
            .AddDefaultUI(UIFramework.Bootstrap4)
            .AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        services.AddScoped<IDownloadRepository, DownloadRepository>();
        services.AddScoped<IDownloadHandler, DownloadHandler>();

    }`

Here is DownloadsController.cs ` using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DalSoft.Hosting.BackgroundQueue; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Updater1.BackgroundTasks; using Updater1.DAL; using Updater1.Models;

namespace Updater1.Controllers { public class DownloadsController : Controller { private readonly db_productpriceContext _context; private readonly BackgroundQueue _backgroundQueue; private readonly IDownloadRepository _downloadRepository; private IServiceProvider _serviceProvider;

    public DownloadsController(db_productpriceContext context, BackgroundQueue backgroundQueue, IDownloadRepository downloadRepository,  IServiceProvider serviceProvider)
    {
        _context = context;
        _downloadRepository = downloadRepository;
        _backgroundQueue = backgroundQueue;
        _serviceProvider = serviceProvider;
    }

....

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create([Bind("Id,FeedId,DateValidUntil")] Download download)
    {
        if (ModelState.IsValid)
        {
            _downloadRepository.InsertDownload(download);
            download.DatedownloadStarted = DateTime.Now;
            _downloadRepository.Save();
            IServiceScopeFactory scopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
            using (var scope = scopeFactory.CreateScope())
            {
                _backgroundQueue.Enqueue(async cancellationToken =>
                {
                    using (var scopedDownloadHandler = scope.ServiceProvider.GetRequiredService<IDownloadHandler>())
                    {
                        await scopedDownloadHandler.DownloadAsync(download);
                    }
                });
            }

            return RedirectToAction(nameof(Index));
        }
        ViewData["FeedId"] = new SelectList(_context.Feed, "Id", "Name", download.FeedId);

        return View(download);
    }

`

I tried your code too but I keep getting Object already disposed on DeQueue method - is this significant.

alexishughes commented 5 years ago

I was trying to use something like this post:

https://github.com/aspnet/DependencyInjection/issues/440

mykjar commented 5 years ago

Is this still going? Awesome Nuget, but I face same DI problem when trying to insert into db. It always states that any custom service I use in same controller is disposed.

DalSoft commented 2 days ago

Fully supported in v2.0.0

Updated in README.md has an example showing how to do this in v.2.0.0

v2.0 has a fully working EF example in the repo