In this article we will be building a .Net 5 console app which support dependency injection, logging and app settings configuration.
You can watch the full Video on Youtube:
And you get the full source code on GitHub:
https://github.com/mohamadlawand087/v22-DotnetConsole
So what's in our agenda today:
- development ingredients
- the functionalities we are going to build
- Coding
Development Ingredients
- Visual Studio Code
- Dotnet Core SDK
Functionalities:
- Dependency Injection
- Serilog Logger
- AppSettings.
We are going to build a sample application which will mimic connecting to a database through dependency injection as well as outputting logs.
We will start by creating our application, inside our terminal
dotnet new console -n "SampleApp"
Once the application has been create, open the application in Visual Studio Code and let us build and the application to make sure everything is working.
dotnet build
dotnet run
The next step is installing the packages that we need.
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Serilog.Extensions.Hosting
dotnet add package Serilog.Settings.Configuration
dotnet add package Serilog.Sinks.Console
The next step will be adding our appsettings.json, to do that in root directory of our application right-click select New File. Name the file appsettings.json
Inside the appsettings we are going to add all of the configuration that we need to setup serilog as well as the connectionString to mimic a database connection
{
"Serilog" : {
"MinimalLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ConnectionStrings": {
"DefaultConnection": "DataSource=app.db;Cache=Shared"
}
}
We will start by implementing the logging mechanism. Inside our Program.cs Add the following code, this code responsibility is reading the appsetting.json and making it available to our application.
static void BuildConfig(IConfigurationBuilder builder)
{
// Check the current directory that the application is running on
// Then once the file 'appsetting.json' is found, we are adding it.
// We add env variables, which can override the configs in appsettings.json
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
}
Now we need to create another method which will be out startup method for our application, it will responsible to put everything together. We will define Serilog as well our dependency injection mechanism in .Net Core.
static IHost AppStartup()
{
var builder = new ConfigurationBuilder();
BuildConfig(builder);
// Specifying the configuration for serilog
Log.Logger = new LoggerConfiguration() // initiate the logger configuration
.ReadFrom.Configuration(builder.Build()) // connect serilog to our configuration folder
.Enrich.FromLogContext() //Adds more information to our logs from built in Serilog
.WriteTo.Console() // decide where the logs are going to be shown
.CreateLogger(); //initialise the logger
Log.Logger.Information("Application Starting");
var host = Host.CreateDefaultBuilder() // Initialising the Host
.ConfigureServices((context, services) => { // Adding the DI container for configuration
})
.UseSerilog() // Add Serilog
.Build(); // Build the Host
return host;
}
Now let us implement data service which will mimic a database
Let us create a new class called DataService and an interface called IDataService
// Interface
public interface IDataService
{
void Connect();
}
// Class
public class DataService : IDataService
{
private readonly ILogger<DataService> _log;
private readonly IConfiguration _config;
public DataService(ILogger<DataService> log, IConfiguration config)
{
_log = log;
_config = config;
}
public void Connect()
{
// Connect to the database
var connectionString = _config.GetValue<string>("ConnectionStrings:DefaultConnection");
_log.LogInformation("Connection String {cs}", connectionString);
}
}
Now we need to update our AppStartup method in the Program.cs class to inject the DataService
var host = Host.CreateDefaultBuilder() // Initialising the Host
.ConfigureServices((context, services) => { // Adding the DI container for configuration
**services.AddTransient<IDataService, DataService>(); // Add transiant mean give me an instance each it is being requested.**
})
.UseSerilog() // Add Serilog
.Build(); // Build the Host
And finally let us put everything together in our main method
static void Main(string[] args)
{
var host = AppStartup();
var service = ActivatorUtilities.CreateInstance<DataService>(host.Services);
service.Connect();
}
Please let me know if you want me to jump into more details about any part of this application or if there is a specific feature which you would like me to cover.
Thanks for reading
Top comments (6)
Thanks for a great walkthrough. It really helped me get started with DI in .NET.
I'm just a bit confused why you're using
var service = ActivatorUtilities.CreateInstance<DataService>(host.Services);
when you've already registered theDataService
class. There you're tying that instance creation to a concrete class, effectively nullifying the benefit of DI. In this example, of course, it doesn't really matter because you're doing the registration and instantiation in code and even in the same class, but if you were to move to something like runtime configuration of what implementation provides theIDataService
implementation then this would be the wrong way to go.After a bit of digging around I found that I could do the following which I think might be a better demonstration for this example:
var service = ActivatorUtilities.GetServiceOrCreateInstance<IDataService>(host.Services);
There, you're actually requesting the registered implementation of the
IDataService
interface, rather than instantiating a concrete class.Great article! Thanks!
Let me add a quick point about the appsettings.json file: to include that file in the console application you have to right-click on it, click on Properties, and finally select Copy Always
Ugh! I'm such a newbie! My little application works great and picks up all the _config values needed if I run it directly (from cmd line, thru a shortcut, or just double-clicking on the exe). But when I run it from another app (using Process), it interprets the base Directory as the directory of the calling process, and so does not pick up my _config settings from appsettings.json. (I can get the _config info for Logger, because I have the full path hard-coded into builder, but in the rest of the code, the _config data returns null. How to get past this so I can keep my appsettings separate and easily modifiable? Thanks.
Thanks for a great post, and especially the video, which finally made a lot of this more understandable. FYI - I answered my own question, I needed to set the properties for appsettings.json to Copy if Newer. Orig Question: Q: how to reference the appsettings json file if it is in the project folder, not the bin/debug/net5/ folder (as with a typical scaffolded net 5 app). ?
Good quickstart tutorial. One point that I have is in JSON configuration example you've got "MinimalLevel" while it should be "MinimumLevel".
Github link is not working. Kindly share the full code