3

I need to access the context from this class so I can check some data from the database but I don't know how to transfer it to the service below:

internal class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;



    public TimedHostedService(ILogger<TimedHostedService> logger) //context ?
    {

        _logger = logger; 


    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero,
            TimeSpan.FromSeconds(60));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("Atualização automática");


    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

startup file:

namespace Products
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddCors(o => o.AddPolicy("AllowAllOrigins", builder =>
            {
                builder.AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowAnyOrigin();
            }));

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            services.AddDbContext<Context>(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("LaprDB")));
            services.AddDbContext<ContextUsers>(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("MyDbConnection")));

            services.AddHostedService<TimedHostedService>();

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseCors("AllowAllOrigins");
            app.UseHttpsRedirection();
            app.UseMvc();

        }
    }
}

I searched some solutions with scoped factories but i could not understand any of them. Can someone explain me how can i transfer the context to the TimedHostedService? If you need more info just let me know pls .

shiqo
  • 103
  • 3
  • 12

1 Answers1

12

A Hosted Service is a singleton, which means that only one instance of that class exists for the life of the application.

A Context is scoped, meaning it's designed to have a very short lifespan (only for a specific "scope", like a single HTTP request). It's not good at staying alive indefinitely (there are db connections involved, which you can't guarantee will stay open for the life of the application, for example).

If you inject a Context into another class, the Context will exist for the life of the instance of that class. For a singleton class, that's the life of the application. So this is why you get the exception you do. .NET Core is telling you: "This isn't going to work the way you think it's going to work"

The solution is here: https://stackoverflow.com/a/48368934/1202807

In short, inject a IServiceScopeFactory, which gives you the power to ask the DI engine to give you a scoped class when needed, then it's up to you to keep it around only as long as you need it.

private readonly IServiceScopeFactory _scopeFactory;

public TimedHostedService(ILogger<TimedHostedService> logger, IServiceScopeFactory scopeFactory)
{
    _logger = logger; 
    _scopeFactory = scopeFactory;
}

Then you get your context like this:

using (var scope = scopeFactory.CreateScope())
{
    var context = scope.ServiceProvider.GetRequiredService<Context>();
    //do what you need
}//scope (and context) gets destroyed here

Old answer (which is wrong here, but applies to other types of classes):

Just put it in your constructor and it'll get injected by dependency injection:

public TimedHostedService(ILogger<TimedHostedService> logger, Context context)
{
    _logger = logger; 
    _context = context;
}

It's the services.AddDbContext() lines that makes them available for dependency injection. Just pick type you want, since you've defined two:

services.AddDbContext<Context>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("LaprDB")));
services.AddDbContext<ContextUsers>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("MyDbConnection")));
Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
  • Just 5 seconds faster, then me:) Except I was thinking about `ContextUsers` – vasily.sib Dec 19 '18 at 02:52
  • Doesn't work. It gives me an error . `An unhandled exception of type 'System.InvalidOperationException' occurred in System.Private.CoreLib.dll: 'Cannot consume scoped service 'Products.Models.Context' from singleton 'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'.'` – shiqo Dec 19 '18 at 02:54
  • I see. I understand the problem now. In that case, your answer is here: https://stackoverflow.com/a/48368934/1202807 – Gabriel Luci Dec 19 '18 at 02:58
  • 1
    I added some explanation to my answer so hopefully it's more clear why you have to jump through hoops here. – Gabriel Luci Dec 19 '18 at 03:08