How to Implement Dependency Injection for .Net Core Console Applications
- Date
- Authors
- Name
- Mehdi Hadeli
- @mehdihadeli
Introduction
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:
- 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.
- 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.
Manually
establishing, creating, and managing the lifespan of a dependency injection container for an applicationWithout
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 previousHostBuilder
is replaced withWebApplicationBuilder.Services
builder.Build()
returns a configuredWebApplication
to the variableapp
instead ofhostBuilder.Build()
which return aIHost
. Configure in HostBuilder is replaced with configuration callsdirectly
using app of typeWebApplication
.
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.