Home > Software engineering >  How to run Winforms with BackgroundService and Dependency Injection?
How to run Winforms with BackgroundService and Dependency Injection?

Time:10-06

I'm trying to start a BackgroundService assigned to a services.HostedService of an IHostBuilder.

IHostBuilder definition:

private static IHostBuilder CreateHostBuilder()
    {
        return Host.CreateDefaultBuilder()
            .ConfigureServices(services =>
            {
                services.AddHostedService<LicenseControl>();

                services.AddTransient<Database>();
                services.AddTransient<CoreLicense>();

                services.AddSingleton<FormSelector>();

                services.AddTransient<ClienteForm>();
                services.AddTransient<GastoForm>();
                services.AddTransient<IngredienteForm>();
                services.AddTransient<ProdutoForm>();
                services.AddTransient<ReceitaForm>();
                services.AddTransient<RelatorioForm>();
                services.AddTransient<VendaForm>();
                services.AddTransient<MainForm>();
                services.AddTransient<ConfiguracoesForm>();
                services.AddTransient<UsuarioForm>();

                services.AddSingleton<AuthenticationForm>();
                
                services.AddScoped<IBridgeRepository, BridgeRepository>();
                services.AddScoped<IClienteRepository, ClienteRepository>();
                services.AddScoped<IGastoRepository, GastoRepository>();
                services.AddScoped<IIBGERepository, IBGERepository>();
                services.AddScoped<IIngredienteRepository, IngredienteRepository>();
                services.AddScoped<IProdutoRepository, ProdutoRepository>();
                services.AddScoped<IReceitaRepository, ReceitaRepository>();
                services.AddScoped<IVendaRepository, VendaRepository>();
                services.AddScoped<IUsuarioRepository, UsuarioRepository>();

                services.AddLogging(
                builder =>
                {
                    builder.AddFilter("Microsoft", LogLevel.Warning)
                           .AddFilter("System", LogLevel.Warning)
                           .AddFilter("NToastNotify", LogLevel.Warning)
                           .AddConsole();
                }
                );
            });
    }

but the only possible way I found to start the HostedService is by running this:

await CreateHostBuilder().Build().RunAsync();   

Finally, the problem happens because on this approach, the thread locks on that line and don't let me run the common block code to start the Form itself:

Application.Run(new MainForm());

The same happens if I run the Form first, the thread'd lock on that line and wouldn't let me call the RunAsync of HostBuilder.

I also tried to declare all HostBuilder scope on the Form class and start the BackgroundService on it's constructor, but then I wouldn't be able to run the Async method on a ctor.

What I'm currently trying (but no success yet) is to call the Form without the Application.Run (and still don't know all the side effects by doing this) so I can run the HostBuilder next.

My entire Program class now:

static class Program
{
    [STAThread]
    private static async Task Main()
    {
        Application.SetHighDpiMode(HighDpiMode.SystemAware);
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);            

        var host = CreateHostBuilder().Build();

        host.Services.GetRequiredService<AuthenticationForm>().Show();

        await host.RunAsync();
    }
    private static IHostBuilder CreateHostBuilder()
    {
        return Host.CreateDefaultBuilder()
            .ConfigureServices(services =>
            {
                services.AddHostedService<LicenseControl>();

                services.AddTransient<Database>();
                services.AddTransient<CoreLicense>();

                services.AddSingleton<FormSelector>();

                services.AddTransient<ClienteForm>();
                services.AddTransient<GastoForm>();
                services.AddTransient<IngredienteForm>();
                services.AddTransient<ProdutoForm>();
                services.AddTransient<ReceitaForm>();
                services.AddTransient<RelatorioForm>();
                services.AddTransient<VendaForm>();
                services.AddTransient<MainForm>();
                services.AddTransient<ConfiguracoesForm>();
                services.AddTransient<UsuarioForm>();

                services.AddSingleton<AuthenticationForm>();
                
                services.AddScoped<IBridgeRepository, BridgeRepository>();
                services.AddScoped<IClienteRepository, ClienteRepository>();
                services.AddScoped<IGastoRepository, GastoRepository>();
                services.AddScoped<IIBGERepository, IBGERepository>();
                services.AddScoped<IIngredienteRepository, IngredienteRepository>();
                services.AddScoped<IProdutoRepository, ProdutoRepository>();
                services.AddScoped<IReceitaRepository, ReceitaRepository>();
                services.AddScoped<IVendaRepository, VendaRepository>();
                services.AddScoped<IUsuarioRepository, UsuarioRepository>();

                services.AddLogging(
                builder =>
                {
                    builder.AddFilter("Microsoft", LogLevel.Warning)
                           .AddFilter("System", LogLevel.Warning)
                           .AddFilter("NToastNotify", LogLevel.Warning)
                           .AddConsole();
                }
                );
            });
    }
}

What'd be the workaround for this considering .NET 5 and ignoring BackgroundWorker component?

CodePudding user response:

Solution by Nkosi, on comments:

Make another hosted service to invoke Application.Run that way when you run the host, the UI aspect and background worker hosted services will be started

class StartProgram : BackgroundService
{
    private readonly IServiceProvider _services;
    public StartProgram(IServiceProvider services)
    {
        this._services = services;
    }
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        Application.SetHighDpiMode(HighDpiMode.SystemAware);
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run((AuthenticationForm)_services.GetService(typeof(AuthenticationForm)));

        return Task.CompletedTask;
    }
}

and the Program.cs:

static class Program
{
    [STAThread]
    private static async Task Main()
    {
        await CreateHostBuilder().Build().RunAsync();
    }
    private static IHostBuilder CreateHostBuilder()
    {
        return Host.CreateDefaultBuilder()
            .ConfigureServices(services =>
            {
                services.AddHostedService<LicenseControl>();
                services.AddHostedService<StartProgram>();

                #region ETC...
            });
    }
}

  
  • Related