Startup.Cs in a Self-Hosted .Net Core Console Application

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

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/

How to run .NET Core Console app using generic host builder

EDIT: An update for .NET 6 is below ↓

Not much has changed with .NET 7.


I'd start off with the default worker template. It comes with necessary packages pre-installed. If you already have a project, install Microsoft.Extensions.Hosting package.

dotnet new worker -n MyCli

Then open up the Program.cs and build the host. Remove the Worker hosted service if you don't want to go with the hosted service route.

public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
// remove the hosted service
// services.AddHostedService<Worker>();

// register your services here.
});
}

Build your logic:

internal class MyService
{
// you can also inject other services
private ILogger<MyService> _logger;

public MyService(ILogger<MyService> logger)
{
_logger = logger;
}

public void DoSomething()
{
_logger.LogInformation("Doing something");
}
}

Then register the class inside .ConfigureServices method

Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddTransient<MyService>();
});

Now you can resolve and call it inside the Main method:

public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var myService = host.Services.GetRequiredService<MyService>();
myService.DoSomething();
}

.NET 6 update

With .NET 6, boilerplate is reduced significantly. We can rewrite our Program.cs as:

var host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services => { services.AddTransient<MyService>(); })
.Build();

var my = host.Services.GetRequiredService<MyService>();
await my.ExecuteAsync();

class MyService
{
private readonly ILogger<MyService> _logger;

public MyService(ILogger<MyService> logger)
{
_logger = logger;
}

public async Task ExecuteAsync(CancellationToken stoppingToken = default)
{
_logger.LogInformation("Doing something");
}
}

How to setup the DI container in a .NET Core console app?

The call to BuildServiceProvider should be made after all services are registered.

There's no need to write all of this code though. Since you use so many extensions already, it's better (and easier) to use the generic Host, the same way an ASP.NET Core application does and use its ConfigureServices, ConfigureAppConfiguration methods:

public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureHostConfiguration(configuration =>
{
configuration....;
});
.ConfigureServices((hostContext, services) =>
{
var myConfigurationSection = configuration.GetSection("app");

services.AddSingleton<IValidateOptions<AppOptions>, AppOptionsValidator>();
services.Configure<AppOptions>(myConfigurationSection);

});
}

Configuration is available through the HostBuilderContext.Configuration property.

CreateDefaultBuilder sets the current folder, configures environment variables and the use of appsettings.json files so there's no need to add them explicitly.

Appsettings.json copy settings

In a web app template, appsettings.json files are added automatically with the Build Action property set to Content and the Copy to Output action to Copy if Newer.

There are no such files in a Console app. When a new appsettings.json file is added by hand, its Build Action is None and Copy to Never. When the application is debugged the current directory is bin\Debug. With the default settings, appsettings.json won't be copied to bin/Debug

Build Action will have to change to Content and Copy should be set to Copy if Newer or Copy Always.

Using Startup class in ASP.NET5 Console Application

What you're looking for is the right idea, but I think you'll need to back up a moment.

Firstly, you may have noticed that your default Program class isn't using static methods anymore; this is because the constructor actually gets some dependency injection love all on its own!

public class Program
{
public Program(IApplicationEnvironment env)
{
}

public void Main(string[] args)
{
}
}

Unfortunately, there aren't as many of the services you're used to from an ASP.NET 5 hosting environment registered; thanks to this article and the IServiceManifest you can see that there's only a few services available:

Microsoft.Framework.Runtime.IAssemblyLoaderContainer
Microsoft.Framework.Runtime.IAssemblyLoadContextAccessor
Microsoft.Framework.Runtime.IApplicationEnvironment
Microsoft.Framework.Runtime.IFileMonitor
Microsoft.Framework.Runtime.IFileWatcher
Microsoft.Framework.Runtime.ILibraryManager
Microsoft.Framework.Runtime.ICompilerOptionsProvider
Microsoft.Framework.Runtime.IApplicationShutdown

This means you'll get the joy of creating your own service provider, too, since we can't get the one provided by the framework.

private readonly IServiceProvider serviceProvider;

public Program(IApplicationEnvironment env, IServiceManifest serviceManifest)
{
var services = new ServiceCollection();
ConfigureServices(services);
serviceProvider = services.BuildServiceProvider();
}

private void ConfigureServices(IServiceCollection services)
{
}

This takes away a lot of the magic that you see in the standard ASP.NET 5 projects, and now you have the service provider you wanted available to you in your Main.

There's a few more "gotchas" in here, so I might as well list them out:

  • If you ask for an IHostingEnvironment, it'll be null. That's because a hosting environment comes from, well, ASP.Net 5 hosting.
  • Since you don't have one of those, you'll be left without your IHostingEnvironment.EnvironmentName - you'll need to collect it from the environment variables yourself. Which, since you're already loading it into your Configuration object, shouldn't be a problem. (It's name is "ASPNET_ENV", which you can add in the Debug tab of your project settings; this is not set for you by default for console applications. You'll probably want to rename that, anyway, since you're not really talking about an ASPNET environment anymore.)

Start a C# web API inside an application

ms recomends this syntax for net 6 minimal apis https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-6.0

app.MapGet("/MyEndpoint", () =>
{
return "it works";
});

as an example, I am using this in my apis , when I am testing them from Visual Studio since I don't like an empty screen.

At first I change launchSettings.json

....
"launchUrl": "welcome",
"applicationUrl": "http://localhost:5000",
....

and this is my welcome code


app.MapGet("/welcome", () =>
{
var con = "<html><body><h1>Hello!</h1><p> <h3> API Is Ready To Work!!! </h3> </p></body></html>";

return Results.Content(con, "text/html");
});

app.Run();


Related Topics



Leave a reply



Submit