【.NET Core】Play with Console App
Tags: csharp, dotnetcore
Published: 2020-08-09 01:16:53

## Intro

I have developed ASP.NET Core applications for several years.
But I have little experience developing Console applications of .Net Core.

So in this time, I try Console application.

Can I develop like ASP.NET Core(ex. using DI, outputting logs, etc)?

### Environments

* .NET Core ver.5.0.100-preview.7.20366.6

## Base project
I create a Console application.

#### Program.cs
```csharp
using System;

namespace ConsoleSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}
```

## Loading Config files
I want to use config values like connection strings for connecting Database.

Of course I can load JSON files with StreamReader, etc. But is there any simple way like ASP.NET Core?

#### Startup.cs
```csharp
...
    private readonly IConfiguration configuration;
    public Startup(IHostEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", false, true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", false, true)
            .AddEnvironmentVariables();
        configuration = builder.Build();
    }
...
```

### Install
To use ConfigurationBuilder, I installed some packages.

* Microsoft.Extensions.Configuration ver.5.0.0-preview.7.20364.11
* Microsoft.Extensions.Configuration.FileExtensions ver.5.0.0-preview.7.20364.11
* Microsoft.Extensions.Hosting ver.5.0.0-preview.7.20364.11
* Microsoft.Extensions.Configuration.Json ver.5.0.0-preview.7.20364.11

### Samples
I add JSON files, and loading functions into Program.cs.

#### appsettings.json
```json
{
    "ConnectionStrings": "Host=localhost;Port=5432;Database=Example;Username=postgres;Password=example"
}
```
#### appsettings.Development.json
```json
{
    "Message": "Hello Development"
}
```
#### appsettings.Production.json
```json
{
    "Message": "Hello Production"
}
```
#### Program.cs
```csharp
using System;
using Microsoft.Extensions.Configuration;

namespace ConsoleSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = GetConfiguration();
            Console.WriteLine(config["ConnectionStrings"]);
            Console.WriteLine("Hello World!");
        }
        private static IConfiguration GetConfiguration()
        {
            var builder = new ConfigurationBuilder()
            .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
            .AddJsonFile("appsettings.json", false, true)
            .AddEnvironmentVariables();
            return builder.Build();
        }
    }
}
```
* [ConfigurationBuilder Class (Microsoft.Extensions.Configuration) | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.configurationbuilder?view=dotnet-plat-ext-3.1)
* [FileConfigurationExtensions.SetBasePath(IConfigurationBuilder, String) Method (Microsoft.Extensions.Configuration) | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.fileconfigurationextensions.setbasepath?view=dotnet-plat-ext-3.1)

I also can use "System.IO.Directory.GetCurrentDirectory()" to set Base Path.

### Get EnvironmentName
I want to load "appsettings.Development.json" for Debug mode.
And When I execute with Release mode, I want to load "appsettings.Production.json".

For ASP.NET Core projects, it may be set EnvironmentName automatically.

* [Working with Multiple Environments — ASP.NET documentation](https://aspnetcore.readthedocs.io/en/stable/fundamentals/environments.html)

So if I set EnvironmentName, I can get EnvironmentName like this.

```csharp
var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
```

Is there more simple way?
So I choose using #if preprocessor directive.

```csharp
        private static IConfiguration GetConfiguration()
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
                .AddJsonFile("appsettings.json", false, true)
#if DEBUG
                .AddJsonFile($"appsettings.Development.json", false, true)
#else
                .AddJsonFile($"appsettings.Production.json", false, true)
#endif
                .AddEnvironmentVariables();
            return builder.Build();
        }
```

* [#if preprocessor directive - C# Reference | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-if)

## DI(Dependency Injection)
I also can use DI in Console applications.

### Install
* Microsoft.Extensions.DependencyInjection ver.5.0.0-preview.7.20364.11

### Samples
#### IProductService.cs
```csharp
namespace Products
{
    public interface IProductService
    {       
    }
}
```
#### ProductService.cs
```csharp
namespace Products
{
    public class ProductService: IProductService
    {       
    }
}
```
#### MainController.cs
```csharp
using System;
using System.Threading.Tasks;
using Products;

namespace Controllers
{
    public class MainController
    {
        private readonly IProductService _product;
        public MainController(IProductService product)
        {
            _product = product;
        }
        public async Task StartAsync()
        {
            await Task.Run(() => Console.WriteLine(_product == null));
        }
    }
}
```
#### Program.cs
```csharp
using System;
using System.Threading.Tasks;
using Controllers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Products;

namespace ConsoleSample
{
    class Program
    {
        static async Task Main(string[] args)
        {
...
            var servicesProvider = BuildDi();
            using (servicesProvider as IDisposable)
            {
                var mainController = servicesProvider.GetRequiredService<MainController>();
                await mainController.StartAsync();
            }
        }
...
        private static IServiceProvider BuildDi()
        {
            var services = new ServiceCollection();
            services.AddTransient<MainController>();
            services.AddScoped<IProductService, ProductService>();
            return services.BuildServiceProvider();
        }
    }
}
```
### Inject IConfiguration
In ASP.NET Core applications, I can use IConfiguration in Controller classes without constructor injection.
But in Console applications, I must add it into IServiceProvider.

#### Program.cs
```csharp
...
        private static IServiceProvider BuildDi()
        {
            var services = new ServiceCollection();

            services.AddSingleton<IConfiguration>(GetConfiguration());
            
            services.AddTransient<MainController>();
            services.AddScoped<IProductService, ProductService>();
            return services.BuildServiceProvider();
        }
...
```
Now I can get IConfiguration by constructor injection.
#### MainController.cs
```csharp
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Products;

namespace Controllers
{
    public class MainController
    {
        private readonly IProductService _product;
        public MainController(IConfiguration config,
            IProductService product)
        {

            Console.WriteLine(config["Message"]); // <- print "Hello Development"
            _product = product;
        }
```
### using Entity Framework Core
As same as ASP.NET Core, I can use Entity Framework Core to access Database.

#### Install
* Microsoft.EntityFrameworkCore ver.5.0.0-preview.7.20365.15
* Microsoft.EntityFrameworkCore.Relational ver.5.0.0-preview.7.20365.15
* Microsoft.EntityFrameworkCore.Abstractions ver.5.0.0-preview.7.20365.15
* Npgsql.EntityFrameworkCore.PostgreSQL ver.5.0.0-preview7-ci.20200722t163648

#### ConsoleSampleContext.cs
```csharp
using Microsoft.EntityFrameworkCore;

namespace Models
{
    public class ConsoleSampleContext: DbContext
    {
        public ConsoleSampleContext(DbContextOptions<ConsoleSampleContext> options)
            :base(options)
        {
        }
    }
}
```
#### Program.cs
```csharp
...
        private static IServiceProvider BuildDi()
        {
            var config = GetConfiguration();
            var services = new ServiceCollection();
            services.AddSingleton<IConfiguration>(config);
            
            services.AddDbContext<ConsoleSampleContext>(options =>
                options.UseNpgsql(config["ConnectionStrings"]));
            
            services.AddTransient<MainController>();
            services.AddScoped<IProductService, ProductService>();
            return services.BuildServiceProvider();
        }
...
```

* [Getting started with .NET Core 2 Console application · NLog/NLog Wiki · GitHub](https://github.com/NLog/NLog/wiki/Getting-started-with-.NET-Core-2---Console-application)

## Output logs
I use NLog to output logs.

### Install
* NLog ver.4.7.3
* NLog.Extensions.Logging ver.1.6.4

### Samples
#### nlog.config
```xml
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true">

    <targets>
        <target xsi:type="Console" name="outputconsole"
            layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />

        <target xsi:type="File" name="outputfile" fileName="C:\tmp\logs\ConsoleSample\${date:format=yyyy}\${date:format=MM}\${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
    </targets>

    <rules>
        <logger name="*" minlevel="Debug" writeTo="outputconsole" />
        <!--Microsoft.* のクラスの Info レベル以下のログはスキップ-->
        <logger name="Microsoft.*" maxLevel="Info" final="true" />
        <logger name="*" minlevel="Debug" writeTo="outputfile" />
    </rules>
</nlog>
```
#### ConsoleSample.csproj
```xml
...
  <ItemGroup>
    <Content Include="nlog.config">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Content Include="appsettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Content Include="appsettings.Development.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Content Include="appsettings.Production.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
</Project>
```

* [Getting started with .NET Core 2 Console application · NLog/NLog Wiki · GitHub](https://github.com/NLog/NLog/wiki/Getting-started-with-.NET-Core-2---Console-application)

### NLog ver.5.0.0-beta11
I got error with NLog ver.5.0.0-beta11.


```
Attempt by method 'NLog.Extensions.Logging.ConfigureExtensions.CreateNLogLoggerProvider(System.IServiceProvider, Microsoft.Extensions.Configuration.IConfiguration, NLog.Extensions.Logging.NLogProviderOptions, NLog.LogFactory)' to access method 'NLog.LogManager.get_LogFactory()' failed.
```

Maybe because some changes of ver.5.0.0.

At least now(2020-08-09), if you want to use these samples. you should use ver.4.7.3.