loading...

【.NET Core】Play with Console App

masanori_msl profile image Masui Masanori ・5 min read

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

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

...
    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

{
    "ConnectionStrings": "Host=localhost;Port=5432;Database=Example;Username=postgres;Password=example"
}

appsettings.Development.json

{
    "Message": "Hello Development"
}

appsettings.Production.json

{
    "Message": "Hello Production"
}

Program.cs

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();
        }
    }
}

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.

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

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

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

        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();
        }

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

namespace Products
{
    public interface IProductService
    {       
    }
}

ProductService.cs

namespace Products
{
    public class ProductService: IProductService
    {       
    }
}

MainController.cs

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

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

...
        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

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

using Microsoft.EntityFrameworkCore;

namespace Models
{
    public class ConsoleSampleContext: DbContext
    {
        public ConsoleSampleContext(DbContextOptions<ConsoleSampleContext> options)
            :base(options)
        {
        }
    }
}

Program.cs

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

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 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

...
  <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>

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.

Discussion

pic
Editor guide