Startup.cs in a self-hosted .NET Core Console Application

C#Console Application.Net Core

C# Problem Overview


I have a self-hosted .NET Core Console Application.

The web shows examples for ASP.NET Core but I do not have a web server. Just a simple command line application.

Is it possible to do something like this for console applications?

public static void Main(string[] args)
{
    // I don't want a WebHostBuilder. Just a command line

    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .Build();

    host.Run();
}

I would like to use a Startup.cs like in ASP.NET Core but on console.

How do I to this?

C# Solutions


Solution 1 - C#

So i came across with this solution, inspired by the accepted answer:

Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        IServiceCollection services = new ServiceCollection();
        // Startup.cs finally :)
        Startup startup = new Startup();
        startup.ConfigureServices(services);
        IServiceProvider serviceProvider = services.BuildServiceProvider();

        //configure console logging
        serviceProvider
            .GetService<ILoggerFactory>()
            .AddConsole(LogLevel.Debug);

        var logger = serviceProvider.GetService<ILoggerFactory>()
            .CreateLogger<Program>();

        logger.LogDebug("Logger is working!");

        // Get Service and call method
        var service = serviceProvider.GetService<IMyService>();
        service.MyServiceMethod();
    }
}

Startup.cs

public class Startup
{
    IConfigurationRoot Configuration { get; }

    public Startup()
    {
        var builder = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json");

        Configuration = builder.Build();
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddLogging();
        services.AddSingleton<IConfigurationRoot>(Configuration);
        services.AddSingleton<IMyService, MyService>();
    }
}

appsettings.json

{
    "SomeConfigItem": {
        "Token": "8201342s223u2uj328",
        "BaseUrl": "http://localhost:5000"
    }
}

MyService.cs

public class MyService : IMyService
{
    private readonly string _baseUrl;
    private readonly string _token;
    private readonly ILogger<MyService> _logger;

    public MyService(ILoggerFactory loggerFactory, IConfigurationRoot config)
    {
        var baseUrl = config["SomeConfigItem:BaseUrl"];
        var token = config["SomeConfigItem:Token"];

        _baseUrl = baseUrl;
        _token = token;
        _logger = loggerFactory.CreateLogger<MyService>();
    }

    public async Task MyServiceMethod()
    {
        _logger.LogDebug(_baseUrl);
        _logger.LogDebug(_token);
    }
}

IMyService.cs

public interface IMyService
{
    Task MyServiceMethod();
}

Solution 2 - C#

This answer is based on the following criteria:

>I'd like to use the new Generic Host CreateDefaultBuilder without any of the ASP.NET web stuff, in a simple console app, but also be able to squirrel away the startup logic in startup.cs in order to configure AppConfiguration and Services

So I spent the morning figuring out how you could do such a thing. This is what I came up with...

The only nuget package this method requires is Microsoft.Extensions.Hosting (at the time of this writing it was at version 3.1.7). Here is a link to the nuget package. This package is also required to use CreateDefaultBuilder(), so chances are you already had it added.

After you add the extension (extension code at bottom of answer) to your project, you set your program entry to look similar to this:

using Microsoft.Extensions.Hosting;

class Program
{
	static async Task Main(string[] args)
	{
		var host = CreateHostBuilder(args).Build();
		await host.RunAsync();
	}

	public static IHostBuilder CreateHostBuilder(string[] args) =>
		Host.CreateDefaultBuilder(args)
		    .UseStartup<Startup>(); // our new method!
}

You add a Startup.cs that should look like this:

public class Startup
{
	public IConfiguration Configuration { get; }

	public Startup(IConfiguration configuration)
	{
		Configuration = configuration;
	}

	public void ConfigureServices(IServiceCollection services)
	{
		// Configure your services here
	}
}

You then configure your services as you would in a typical ASP.NET Core application (without needing to have ASP.NET Core Web Hosting installed).

Demo Project

I put together a .NET Core 3.1 console demo project doing all kinds of things such as an IHostedService implementation, BackgroundService implementation, transient/singleton services. I also injected in IHttpClientFactory and IMemoryCache for good measure.

Clone that repo and give it a shot.

How It Works

I created a IHostBuilder extension method which simply implements the IHostBuilder UseStartup<TStartup>(this IHostBuilder hostBuilder) pattern that we are all used to.

Since CreateDefaultBuilder() adds in all the basics, there's not much left to add to it. The only thing we are concerned about is getting the IConfiguration and creating our service pipeline via ConfigureServices(IServiceCollection).

Extension Method Source Code

/// <summary>
/// Extensions to emulate a typical "Startup.cs" pattern for <see cref="IHostBuilder"/>
/// </summary>
public static class HostBuilderExtensions
{
	private const string ConfigureServicesMethodName = "ConfigureServices";

	/// <summary>
	/// Specify the startup type to be used by the host.
	/// </summary>
	/// <typeparam name="TStartup">The type containing an optional constructor with
	/// an <see cref="IConfiguration"/> parameter. The implementation should contain a public
	/// method named ConfigureServices with <see cref="IServiceCollection"/> parameter.</typeparam>
	/// <param name="hostBuilder">The <see cref="IHostBuilder"/> to initialize with TStartup.</param>
	/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
	public static IHostBuilder UseStartup<TStartup>(
		this IHostBuilder hostBuilder) where TStartup : class
	{
		// Invoke the ConfigureServices method on IHostBuilder...
		hostBuilder.ConfigureServices((ctx, serviceCollection) =>
		{
			// Find a method that has this signature: ConfigureServices(IServiceCollection)
			var cfgServicesMethod = typeof(TStartup).GetMethod(
				ConfigureServicesMethodName, new Type[] { typeof(IServiceCollection) });

			// Check if TStartup has a ctor that takes a IConfiguration parameter
			var hasConfigCtor = typeof(TStartup).GetConstructor(
				new Type[] { typeof(IConfiguration) }) != null;

			// create a TStartup instance based on ctor
			var startUpObj = hasConfigCtor ?
				(TStartup)Activator.CreateInstance(typeof(TStartup), ctx.Configuration) :
				(TStartup)Activator.CreateInstance(typeof(TStartup), null);

			// finally, call the ConfigureServices implemented by the TStartup object
			cfgServicesMethod?.Invoke(startUpObj, new object[] { serviceCollection });
		});

		// chain the response
		return hostBuilder;
	}
}

Solution 3 - C#

All .NET Core applications are composed of well-crafted independent libraries and packages which you're free to reference and use in any type of application. It just so happens that an Asp.net core application comes preconfigured to reference a lot of those libraries and exposes an http endpoint.

But if it's Dependency Injection you need for your console app, simply reference the appropriate library. Here's a guide: https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/

Solution 4 - C#

Another way would be using HostBuilder from Microsoft.Extensions.Hosting package.

public static async Task Main(string[] args)
{
    var builder = new HostBuilder()
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            config.SetBasePath(Directory.GetCurrentDirectory());
            config.AddJsonFile("appsettings.json", true);
            if (args != null) config.AddCommandLine(args);
        })
        .ConfigureServices((hostingContext, services) =>
        {
            services.AddHostedService<MyHostedService>();
        })
        .ConfigureLogging((hostingContext, logging) =>
        {
            logging.AddConfiguration(hostingContext.Configuration);
            logging.AddConsole();
        });

    await builder.RunConsoleAsync();
}

Solution 5 - C#

I know this thread is kinda old, but I decided to share my code anyway, since it also accomplishes the end Daniel wanted (DI in a Console Application), but without a Startup class. Ps.: Note that this solution is valid either for .NET Core or .NET Framework.

Program.cs:

public class Program
    {

        public static void Main(string[] args)
        {
            var services = new ServiceCollection();

            DependencyInjectionConfiguration.ConfigureDI(services);

            var serviceProvider = services.BuildServiceProvider();

            var receiver = serviceProvider.GetService<MyServiceInterface>();
                
            receiver.YourServiceMethod();
        }
    }

public static class DependencyInjectionConfiguration
    {
        public static void ConfigureDI(IServiceCollection services)
        {
            services.AddScoped<MyServiceInterface, MyService>();
            services.AddHttpClient<MyClient>(); // for example
        }
    }

Solution 6 - C#

I came across the same problem and I think this is a good solution:

class Program
{
    static async Task Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();
        await host.RunAsync();
    }

     public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostBuilderContext, serviceCollection)
             => new Startup(hostBuilderContext.Configuration)
            .ConfigureServices(serviceCollection))
}

Solution 7 - C#

Yes, it is. ASP.NET Core applications can either be self-hosted - as in your example - or hosted inside a web server such as IIS. In .NET Core all apps are console apps.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDanielView Question on Stackoverflow
Solution 1 - C#DanielView Answer on Stackoverflow
Solution 2 - C#AndyView Answer on Stackoverflow
Solution 3 - C#Arash MotamediView Answer on Stackoverflow
Solution 4 - C#RajView Answer on Stackoverflow
Solution 5 - C#Pedro CoelhoView Answer on Stackoverflow
Solution 6 - C#marianogView Answer on Stackoverflow
Solution 7 - C#Ricardo PeresView Answer on Stackoverflow