Search code examples
c#.netwinformsdependency-injectionbackground-service

How to run Winforms with BackgroundService and Dependency Injection?


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?


Solution

  • 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...
                });
        }
    }