dotnet-core

How to Implement Dependency Injection for .Net Core Console Applications

How to Implement Dependency Injection for .Net Core Console Applications

If you’re developing a.NET Core console application, you might notice that it lacks support out of the box for crucial features like dependency injection, logging, and [configuration]. But by using these strategies, you can organize your console application and make it simpler to maintain, just like in an ASP.NET Core application. This kind of console app could be used for straightforward microservices that do activities like “processing messages” from queues and “enriching data.”

Are you trying to manage microservices in an easy and effective way? Console programs are the only thing you need. Console programs have the benefit of being used for background tasks, such as handling queued messages or doing data enrichment. Since minimal APIs are created specifically to handle HTTP requests and responses, this is not possible with them. Console programs are ideal for microservices since they are small and simple to deploy. They enable the speedy development and deployment of microservices that carry out particular functions without the overhead of a more substantial application.

We’ll look at how dependency injection, logging, and configuration may be used in your.NET Core console application in this blog post. Consequently, read on to learn more if you’re a seasoned developer or are just getting started with.NET Core.

Fortunately, your.NET Core console application may combine dependency injection, logging, and configuration using a variety of methods:

  1. Making use of the Generic HostBuilder and [HostBuilder] (https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.hostbuilder) classes offered by the.NET Core framework. Building console apps that take advantage of dependency injection may be done using the versatile and highly configurable Generic HostBuilder. In a hosted environment, it offers a practical method for configuring and running an application. A variety of configuration options are supported by the HostBuilder, including dependency injection, configuration providers, logging, and application lifecycle management.
  2. Using .NET 6.0’s WebApplicationBuilder and WebApplication. Because it is portable, light, and easy to use, this new hosting architecture is particularly well-suited for developing simple APIs. Using the WebApplication and WebApplicationBuilder classes, we configure and launch our console application in this function.
  3. Manually establishing, creating, and managing the lifespan of a dependency injection container for an application Without Host Builder, It gives more flexibility and is less rigid, nevertheless. This approach necessitates more manual setup.

Using the HostBuilder class

Without Background Worker

The .NET Core framework’s HostBuilder class is a great tool for building and configuring the host environment for our console application. We may configure services like dependency injection, logging, and configuration, as well as build a generic host for our terminal application by using the HostBuilder. Larger console applications that demand a more systematic and organized approach to service configuration and management benefit the most from the HostBuilder.

We must include a reference to the ‘Microsoft.Extensions.Hosting’ NuGet package and the necessary serilog packages in order to use generic HostBuilder in our console app:

dotnet add package Microsoft.Extensions.Hosting dotnet add package Serilog.AspNetCore dotnet add package Serilog.Settings.Configuration dotnet add package Serilog.Sinks.Console

To use the HostBuilder, we need to create an instance of the HostBuilder class and configure the services we require. However, for pre-configuring some required features, it’s better to use Host.CreateDefaultBuilder(args) for creating HostBuilder (for web app we also should call ConfigureWebHostDefaults after CreateDefaultBuilder but here we are in the console). This method creates a default HostBuilder instance with some pre-configured settings. Once the services are configured, we can use the Build() method to build the hostBuilder of type IHostBuilder and create a host of type IHost.

It’s important to note that we don't need to call the Run() method on this host of type IHost because we don’t actually want to create a web API on the console. we just want to use HostBuilder internal features for dependency injection, configuration, logging.

Also we use the new .NET 6 minimal hosting model that significantly reduces the number of files and lines of code, also it uses top-level statements to minimize the code required for an app and global using directives to eliminate or minimize the number of using statement lines required.

Here’s a basic generic HostBuilder configuration for a console application:

// https://github.com/serilog/serilog-aspnetcore#two-stage-initialization Log.Logger = new LoggerConfiguration().MinimumLevel .Override("Microsoft", LogEventLevel.Information) .WriteTo.Console() .CreateBootstrapLogger(); // Load some envs like `ASPNETCORE_ENVIRONMENT` for accessing in `HostingEnvironment`, default is Production DotNetEnv.Env.TraversePath().Load(); try { // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host#default-builder-settings var hostBuilder = Host.CreateDefaultBuilder(args); hostBuilder // .ConfigureLogging(logging => // { // logging.AddConsole(); // logging.AddDebug(); // }) .UseSerilog( (context, sp, loggerConfiguration) => { loggerConfiguration.Enrich .WithProperty("Application", context.HostingEnvironment.ApplicationName) .ReadFrom.Configuration(context.Configuration, sectionName: "Serilog") .WriteTo.Console( outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level} - {Message:lj}{NewLine}{Exception}" ); } ) // setup configurations - CreateDefaultBuilder do this for us, but we can override that configuration .ConfigureAppConfiguration(configurationBuilder => { configurationBuilder.AddJsonFile( "appsettings.json", optional: true, reloadOnChange: true ); configurationBuilder.AddEnvironmentVariables(); configurationBuilder.AddCommandLine(args); }) .ConfigureServices( (hostContext, services) => { var configuration = hostContext.Configuration; var environment = hostContext.HostingEnvironment; var appOptions = configuration.GetSection("AppOptions").Get<AppOptions>(); // setup dependencies services.AddOptions<AppOptions>().BindConfiguration(nameof(AppOptions)); services.AddSingleton<MyService>(); services.AddSingleton<ConsoleRunner>(); } ) // build our HostBuilder to IHost var host = hostBuilder.Build(); // run our console app await host.ExecuteConsoleRunner(); // Or // await AppConsoleRunner.RunAsync(host.Services); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }

In this example, we utilize Host.CreateDefaultBuilder(args) to create a default HostBuilder instance and configure our required services and their dependencies using the ConfigureServices method on hostBuilder. Additionally, we can override the default AppConfiguration set by Host.CreateDefaultBuilder(args) with ConfigureAppConfiguration and loading appsettings.json and other configurations. We also set up our log provider on hostBuilder; in this example, we use Serilog with the below configuration and utilize Serilog’s Two-stage initialization:

// https://github.com/serilog/serilog-aspnetcore#two-stage-initialization Log.Logger = new LoggerConfiguration().MinimumLevel .Override("Microsoft", LogEventLevel.Information) .WriteTo.Console() .CreateBootstrapLogger(); try { var hostBuilder = Host.CreateDefaultBuilder(args); hostBuilder.UseSerilog( (context, sp, loggerConfiguration) => { loggerConfiguration.Enrich .WithProperty("Application", context.HostingEnvironment.ApplicationName) .ReadFrom.Configuration(context.Configuration, sectionName: "Serilog") .WriteTo.Console( outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level} - {Message:lj}{NewLine}{Exception}" ); } ) ///... } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }

Alternatively, we can replace serilog with the default .NET log provider or change the default logging provider configurations. the CreateDefaultBuilder internally will register microsoft logger and we don’t need explicitly add it again unless we need to changes some of its configurations:

hostBuilder.ConfigureLogging(logging => { logging.AddConsole(); logging.AddDebug(); //... some other configurations for logs }) ///...

Once we’ve set up all configurations on the hostBuilder of type IHostBuilder, we can use the Build() method to build the host of type IHost. By using the HostBuilder, we can manage the lifetime of our console application and take advantage of the many built-in services provided by the .NET Core framework.

After building our host, we can run our console app with using a AppConsoleRunner static class like this and pass our ServiceProvider form host.Services to RunAsync method of this class.

public static class AppConsoleRunner { public static Task RunAsync(IServiceProvider serviceProvider) { var appOptions = serviceProvider.GetRequiredService<IOptions<AppOptions>>(); Console.WriteLine($"Starting '{appOptions.Value.ApplicationName}' App..."); var myService = serviceProvider.GetRequiredService<MyService>(); myService.DoSomething(); Console.ReadKey(); Console.WriteLine("Application Stopped."); return Task.CompletedTask; } }

If we’re looking for a new way to execute your console app, there’s a handy method to try out. We can simply add a ConsoleRunner class to your dependency injection and then call the ExecuteConsoleRunner extension method on your created host. This can be achieved with a simple command, await host.ExecuteConsoleRunner().

First We need to add ConsoleRunner class to our dependency injection with:

services.AddSingleton<ConsoleRunner>();

Then we’ll need to resolve the ConsoleRunner class from the host using host.Services which is actually a ServiceProvider class. Once we’ve done that, we can then call its ExecuteAsync method to get the desired results. This can be a great way to streamline your console app and make it more efficient.

public class ConsoleRunner { private readonly MyService _service; private readonly AppOptions _options; public ConsoleRunner(IOptions<AppOptions> options, MyService service) { _service = service; _options = options.Value; } public Task ExecuteAsync() { Console.WriteLine($"Starting '{_options.ApplicationName}' App..."); _service.DoSomething(); Console.ReadKey(); Console.WriteLine("Application Stopped."); return Task.CompletedTask; } } public static class Extensions { public static async ValueTask ExecuteConsoleRunner(this IHost host) { var runners = host.Services.GetServices<ConsoleRunner>().ToList(); if (runners.Any() == false) throw new Exception( "Console runner not found, create a console runner with implementing 'IConsoleRunner' interface" ); if (runners.Count > 1) throw new Exception("Console app should have just one runner."); var runner = runners.First(); await runner.ExecuteAsync(); } }

Using HostBuilder With Background Worker

Another way for using generic HostBuilder in console app is using it with a background worker for running our console application and call RunConsoleAsync on the hostBuilder of type IHostBuilder (internally do Build().RunAsync() on HostBuilder) which builds and starts the application on top of a background worker, so we need to create a AppConsoleWorkerRunner for running our console app. It will then keep running until CTRL+C is used to trigger it to shutdown. If we don’t prefer using background worker, use previous approach.

For using generic HostBuilder for our console app we need to add a reference to Microsoft.Extensions.Hosting nuget package and needed packages for serilog:

dotnet add package Microsoft.Extensions.Hosting dotnet add package Serilog.AspNetCore dotnet add package Serilog.Settings.Configuration dotnet add package Serilog.Sinks.Console

Then we need to create an instance of the HostBuilder class and configure the services we require, also for pre-config some required features it is better we use Host.CreateDefaultBuilder(args) for creating host builder. Once the services are configured, we can use the Build() method to build the host, but we don’t call Run() method on this host because we don’t want actually create a web api on the console we just want to use its internal feature.

To use the HostBuilder, we need to create an instance of the HostBuilder class and configure the services we require. However, for pre-configuring some required features, it’s better to use Host.CreateDefaultBuilder(args) for creating HostBuilder (for web app we also should call ConfigureWebHostDefaults after CreateDefaultBuilder but here we are in the console with background service and it is enough). This method creates a default HostBuilder instance with some pre-configured settings. Once the services are configured, we can use the Build() method to build the hostBuilder of type IHostBuilder and create a host of type IHost.

// https://github.com/serilog/serilog-aspnetcore#two-stage-initialization Log.Logger = new LoggerConfiguration().MinimumLevel .Override("Microsoft", LogEventLevel.Information) .WriteTo.Console() .CreateBootstrapLogger(); // Load some envs like `ASPNETCORE_ENVIRONMENT` for accessing in `HostingEnvironment`, default is Production DotNetEnv.Env.TraversePath().Load(); try { // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host#default-builder-settings var hostBuilder = Host.CreateDefaultBuilder(args); hostBuilder .UseSerilog( (context, sp, loggerConfiguration) => { loggerConfiguration.Enrich .WithProperty("Application", context.HostingEnvironment.ApplicationName) .ReadFrom.Configuration(context.Configuration, sectionName: "Serilog") .WriteTo.Console( outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level} - {Message:lj}{NewLine}{Exception}"); }) // setup configurations - CreateDefaultBuilder do this for us, but we can override that configuration .ConfigureAppConfiguration( configurationBuilder => { configurationBuilder.AddJsonFile( "appsettings.json", optional: true, reloadOnChange: true); configurationBuilder.AddEnvironmentVariables(); configurationBuilder.AddCommandLine(args); }) .ConfigureServices( (hostContext, services) => { var configuration = hostContext.Configuration; var environment = hostContext.HostingEnvironment; var appOptions = configuration.GetSection("AppOptions").Get<AppOptions>(); // setup dependencies services.AddOptions<AppOptions>().BindConfiguration(nameof(AppOptions)); services.AddSingleton<MyService>(); // worker for running our console app services.AddHostedService<AppConsoleWorkerRunner>(); }); // running the console app - internally do `Build().RunAsync()` await hostBuilder.RunConsoleAsync(); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }

In this example, we utilize Host.CreateDefaultBuilder(args) to create a default HostBuilder instance and configure our required services and their dependencies using the ConfigureServices method on hostBuilder, here we also need to register our AppConsoleWorkerRunner which is responsible for running our console app with services.AddHostedService<AppConsoleWorkerRunner>(). Additionally, we can override the default AppConfiguration set by Host.CreateDefaultBuilder(args) with ConfigureAppConfiguration and loading appsettings.json and other configurations. We also set up our log provider on hostBuilder; in this example, we use Serilog with the below configuration and utilize Serilog’s Two-stage initialization:

// https://github.com/serilog/serilog-aspnetcore#two-stage-initialization Log.Logger = new LoggerConfiguration().MinimumLevel .Override("Microsoft", LogEventLevel.Information) .WriteTo.Console() .CreateBootstrapLogger(); try { var hostBuilder = Host.CreateDefaultBuilder(args); hostBuilder.UseSerilog( (context, sp, loggerConfiguration) => { loggerConfiguration.Enrich .WithProperty("Application", context.HostingEnvironment.ApplicationName) .ReadFrom.Configuration(context.Configuration, sectionName: "Serilog") .WriteTo.Console( outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level} - {Message:lj}{NewLine}{Exception}" ); } ) ///... } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }

In order to run our console application, we need to call RunConsoleAsync on the HostBuilder. This method internally calls HostBuilder.Build().RunAsync(), which builds and starts the application. It’s important to note that calling RunConsoleAsync will start the host and wait for a Ctrl+C or SIGTERM signal to exit. This means that without explicitly telling the application to exit, it will not exit.

When a host starts, it calls IHostedService.StartAsync on each implementation of IHostedService registered in the service container’s collection of hosted services (all of our workers). In a web app, one of the IHostedService implementations is a web service that starts an HTTP server implementation.

Running the host of Type IHost without any workers and logic do nothing and never exit. So for implementing our console app’s logic we need to implement and register an IHostedService that here is AppConsoleWorkerRunner. we can manage the life time this worker with IHostApplicationLifetime. When an application is started using the IHost, the host registers an implementation of the IHostApplicationLifetime interface with the dependency injection container. This implementation can then be used by other parts of the application to gracefully handle shutdown events.

public class AppConsoleWorkerRunner : IHostedService { private readonly ILogger<AppConsoleWorkerRunner> _logger; private readonly IHostApplicationLifetime _appLifetime; private readonly MyService _service; private readonly AppOptions _options; public AppConsoleWorkerRunner( ILogger<AppConsoleWorkerRunner> logger, IHostApplicationLifetime appLifetime, IOptions<AppOptions> options, MyService service) { _logger = logger; _appLifetime = appLifetime; _service = service; _options = options.Value; } public Task StartAsync(CancellationToken cancellationToken) { _appLifetime.ApplicationStopped.Register( () => { Console.WriteLine("Application Stopped."); }); Console.WriteLine($"Starting '{_options.ApplicationName}' App..."); _service.DoSomething(); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } }

Using the WebApplicationBuilder

In the previous section, we discussed the HostBuilder class and how we can use it to configure and build the host for our console application. However, the examples in that section focused on creating a web application using the generic HostBuilder class. In this section, we’ll focus on using the new hosting model (Minimal) and WebApplicationBuilder that introduced in .Net 6 to build a app of type WebApplication for a console application and see how it can help us to manage the lifetime of our application and take advantage of the many built-in services provided by the .NET Core framework.

The minimal hosting model:

  • Significantly reduces the number of files and lines of code required to create an app. Only one file is needed with four lines of code.
  • Unifies Startup.cs and Program.cs into a single Program.cs file.
  • Uses top-level statements to minimize the code required for an app.
  • Uses global using directives to eliminate or minimize the number of using statement lines required.

The new hosting model with WebApplicationBuilder and WebApplication provides a more flexible and modular way of building hosts that allows us to create console applications with more advanced features like configuration, logging, and dependency injection. The underlying principle of the new hosting model involves the creation of a HostBuilder instance, which is configured to include the services and features necessary for our application, all done behind the scenes.

Take a look at these code snippets that compare the differences between the new hosting model with WebApplicationBuilder and the Generic HostBuilder:

HostBuilder approach:

// create HostBuilder var hostBuilder = Host.CreateDefaultBuilder(args); // setup dependencies hostBuilder .ConfigureServices( (hostContext, services) => { services.AddOptions<AppOptions>().BindConfiguration(nameof(AppOptions)); services.AddSingleton<MyService>(); } ); hostBuilder .ConfigureWebHostDefaults(webBuilder => { webBuilder.Configure(app => { app.UseAuthorization(); }); }); // build our HostBuilder to IHost var host = hostBuilder.Build(); await AppConsoleRunner.RunAsync(host.Services);

WebApplicationBuilder approach:

// create WebApplicationBuilder var builder = WebApplication.CreateBuilder(args); // setup dependencies builder.Services.AddSingleton<MyService>(); // build our WebApplicationBuilder to WebApplication var app = builder.Build(); app.UseAuthorization(); // run our console app await AppConsoleRunner.RunAsync(app);

Some of their difference:

  • ConfigureServices method in previous HostBuilder is replaced with WebApplicationBuilder.Services
  • builder.Build() returns a configured WebApplication to the variable app instead of hostBuilder.Build() which return a IHost. Configure in HostBuilder is replaced with configuration calls directly using app of type WebApplication.

To create a new app of type WebApplication using the new hosting model, we use WebApplication.CreateBuilder initializes a new instance of the WebApplicationBuilder class with pre-configured defaults (it acts like Host.CreateDefaultBuilder(args) in generic HostBuilder).

It’s important to note that you don't need to call the Run() method on this app of type WebApplication because you don’t actually want to create a web API on the console. You just want to use WebApplicationBuilder internal features for dependency injection, configuration, logging.

For using WebApplicationBuilder for our console app we need to add a reference to Microsoft.Extensions.Hosting nuget package and needed packages for serilog:

dotnet add package Microsoft.Extensions.Hosting dotnet add package Serilog.AspNetCore dotnet add package Serilog.Settings.Configuration dotnet add package Serilog.Sinks.Console

Here’s a WebApplicationBuilder configuration for a console application:

// https://github.com/serilog/serilog-aspnetcore#two-stage-initialization Log.Logger = new LoggerConfiguration().MinimumLevel .Override("Microsoft", LogEventLevel.Information) .WriteTo.Console() .CreateBootstrapLogger(); // Load some envs like `ASPNETCORE_ENVIRONMENT` for accessing in `HostingEnvironment`, default is Production DotNetEnv.Env.TraversePath().Load(); try { // https://learn.microsoft.com/en-us/aspnet/core/migration/50-to-60?tabs=visual-studio#new-hosting-model var builder = WebApplication.CreateBuilder(args); // setup logs builder.Host.UseSerilog( (context, sp, loggerConfiguration) => { loggerConfiguration.Enrich .WithProperty("Application", context.HostingEnvironment.ApplicationName) .ReadFrom.Configuration(context.Configuration, sectionName: "Serilog") .WriteTo.Console( outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level} - {Message:lj}{NewLine}{Exception}"); }); var configuration = builder.Configuration; var environment = builder.Environment; var appOptions = configuration.GetSection("AppOptions").Get<AppOptions>(); // setup dependencies builder.Services.AddSingleton<MyService>(); builder.Services.AddOptions<AppOptions>().BindConfiguration(nameof(AppOptions)); // build our app of type WebApplication var app = builder.Build(); // run our console app AppConsoleRunner.Run(app); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }

In this example, we utilize WebApplication.CreateBuilder(args) to create a default WebApplicationBuilder instance and configure our required services and their dependencies to our builder of type WebApplicationBuilder with using Services property, something like this:

// setup dependencies builder.Services.AddSingleton<MyService>(); builder.Services.AddOptions<AppOptions>().BindConfiguration(nameof(AppOptions));

We also set up our log provider on the builder; in this example, we use Serilog with the below configuration and utilize Serilog’s Two-stage initialization:

// https://github.com/serilog/serilog-aspnetcore#two-stage-initialization Log.Logger = new LoggerConfiguration().MinimumLevel .Override("Microsoft", LogEventLevel.Information) .WriteTo.Console() .CreateBootstrapLogger(); try { // https://learn.microsoft.com/en-us/aspnet/core/migration/50-to-60?tabs=visual-studio#new-hosting-model var builder = WebApplication.CreateBuilder(args); // setup logs builder.Host.UseSerilog( (context, sp, loggerConfiguration) => { loggerConfiguration.Enrich .WithProperty("Application", context.HostingEnvironment.ApplicationName) .ReadFrom.Configuration(context.Configuration, sectionName: "Serilog") .WriteTo.Console( outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level} - {Message:lj}{NewLine}{Exception}"); }); ///... } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }

Alternatively, you can replace serilog with the default .NET log provider or change the default logging provider configurations. the WebApplication.CreateBuilder(args) internally will register microsoft logger and we don’t need explicitly add it again unless we need to changes some of its configurations:

builder.Host.ConfigureLogging(logging => { logging.AddConsole(); logging.AddDebug(); //... some other configurations for logs }) ///...

After you’ve completed configuring the builder of type WebApplicationBuilder, you can use the Build() method to build the app of type WebApplication. By using the WebApplicationBuilder, you can easily manage the lifetime of your console application and make use of the many built-in services provided by the .NET Core framework. Compared to the generic HostBuilder, this approach is more straightforward and concise.

After building our app, we can run our console app with using a AppConsoleRunner static class like this and pass our WebApplication form app to RunAsync method of this class.

public static class AppConsoleRunner { public static Task RunAsync(WebApplication app) { // run our console app var appOptions = app.Services.GetRequiredService<IOptions<AppOptions>>(); Console.WriteLine($"Starting '{appOptions.Value.ApplicationName}' App..."); var myService = app.Services.GetRequiredService<MyService>(); myService.DoSomething(); Console.ReadKey(); Console.WriteLine("Application Stopped."); return Task.CompletedTask; } }

Using Without the HostBuilder

Although the HostBuilder provides a convenient way to configure and manage the lifetime of your console application, it is not always necessary to use it. In some cases, such as when you prefer a simpler approach, you can build a console application without the HostBuilder.

For this purpose we need to add a reference to following nuget packages:

dotnet add package Microsoft.Extensions.Logging dotnet add package Microsoft.Extensions.DependencyInjection dotnet add package Microsoft.Extensions.Configuration dotnet add package Microsoft.Extensions.Configuration.Json dotnet add package Microsoft.Extensions.Options dotnet add package Microsoft.Extensions.Options.ConfigurationExtensions dotnet add package Serilog dotnet add package Serilog.Extensions.Logging dotnet add package Serilog.Settings.Configuration dotnet add package Serilog.Sinks.Console

Here’s an example of how to build a console application without the HostBuilder while still configuring logging, configuration, and dependency injection:

// Load some envs like `ASPNETCORE_ENVIRONMENT` DotNetEnv.Env.TraversePath().Load(); IConfiguration configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: false) .Build(); // Configure Serilog Log.Logger = new LoggerConfiguration() .MinimumLevel.Override("Microsoft", LogEventLevel.Information) .Enrich.WithProperty("Application", "ConsoleAppWithoutHostBuilder") .ReadFrom.Configuration(configuration, sectionName: "Serilog") .WriteTo.Console( outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level} - {Message:lj}{NewLine}{Exception}" ).CreateLogger(); try { // Create service collection IServiceCollection services = new ServiceCollection(); // setup dependencies // Add logging services.AddLogging( loggingBuilder => { loggingBuilder.ClearProviders(); loggingBuilder.AddSerilog(dispose: true, logger: Log.Logger); }); // Add Configuration services.AddSingleton(configuration); services.AddOptions<AppOptions>().BindConfiguration(nameof(AppOptions)); services.AddTransient<MyService>(); // Build service provider IServiceProvider serviceProvider = services.BuildServiceProvider(); // Run the console app await AppConsoleRunner.RunAsync(serviceProvider); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }

In this example, we start by setting up our configuration. We first create an instance of the IConfiguration interface by building a ConfigurationBuilder that reads from the appsettings.json file. Next, we configure Serilog to output to the console and read from the configuration file.

After that, we create an instance of IServiceCollection called services and use it to register all of our required services for dependency injection. We add our logging and configuration services to IServiceCollection, and then register all of our other services as well.

This approach allows us to use dependency injection in our console application, making it easier to manage and test our application’s components.

Source Code

All source code with different approaches are available in This Repository on the GitHub.

Conclusion

In conclusion, we have explored three different approaches for building console applications in .NET Core: using the HostBuilder, the WebApplicationBuilder, and building a console application without the HostBuilder. Each approach has its own pros and cons, depending on the complexity and requirements of your application. You can use of each of these approach based on your need and tradeoff.

The HostBuilder approach is ideal for more complex applications that require advanced features such as configuration, logging, and dependency injection. We can use it with or without a Worker process for using in a console application. It provides a modular and extensible architecture, which allows for easy integration of additional services and components. However, it requires a bit more setup and configuration, and may not be the most straightforward option for simple console applications.

The WebApplicationBuilder approach is a more straightforward and compact alternative, which is well-suited for simpler console applications that don’t require the advanced features of the HostBuilder. It also provides a simpler way to manage the lifetime of your application. However, it is limited to the services and components provided by the WebApplicationBuilder, and may not be the most flexible option for more complex applications.

Finally, building a console application without the HostBuilder provides the most flexibility and control over your application’s architecture, but also requires more manual setup and configuration. It may be a good option for developers who want complete control over their application’s architecture, or for applications with specific requirements that are not met by the HostBuilder or WebApplicationBuilder approaches.

Ultimately, the choice of approach will depend on the specific needs of your application, and each approach has its own advantages and disadvantages. It is recommended to carefully consider your application’s requirements and the pros and cons of each approach before deciding which one to use.

References

Mehdi

Mehdi

Hey, I'm Mehdi Hadeli, a .NET and Golang Software Engineer, interested in cutting-edge technologies, Microservices, DDD, CQRS, Event Sourcing, Event Driven Architecture.