This is just an overview of what makes .NET Core apps work with links pointing to a much more in-depth coverage of these concepts. There are several types of .NET Core apps but this only covers the common ones, or at least those I have worked with. Hopefully this will give you a much better understanding of the scope involved in developing .NET Core apps. Let’s go straight to it then.

.NET Generic Host

.NET Core apps uses this .NET Generic Host to handle the apps’ resources such as:

They are 2 versions of this host and are created by these 2 builders:

.NET Core apps such as the Worker Services and even Console apps use the HostBuilder version while ASP.NET Core apps such as Web APIs and MVC apps use the WebApplicationBuilder version.

Historically, this host was originally developed for ASP.NET Core apps but was extended for use with other types of .NET Core apps.

Let’s look at the different .NET Core apps generated from templates provided in the Visual Studio 2022 Community Edition and see what type of host they use and how it is used in it’s basic setup.

Console App

Here is the generated code from the Console App template with the usual startup code or entry point for any .NET Core app found in Program.cs file.

/* Program.cs */

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

It does not actually contain code that uses a host. You will have to add it yourself which is not that difficult once you looked at the generated Worker Service code later on.

What you will notice in the code is the missing Main method. This is the new form in .NET 6. See .NET 6 C# console app template generates top-level statements for more information on this.

Worker Service

Here is the generated code from the Worker Service template:

/* Program.cs */

using WorkerService1;

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
    })
    .Build();

await host.RunAsync();
/* Worker.cs */

namespace WorkerService1
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

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

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(1000, stoppingToken);
            }
        }
    }
}

In the Program.cs file,

  1. the Host.CreateDefaultBuilder() returns an instance of the HostBuilder class.
  2. From this host you can then add services to the (dependency injection) container via ConfigureServices().
  3. Then you register your own implementation of an IHostedService which in this case is the Worker in the Worker.cs file via AddHostedService<T>().
  4. Then you Build() your host.
  5. And lastly, call Run() or RunAsync() on the host to run all registered hosted services, because you can have multiple hosted services running and not just one.

The Worker.cs file, on the otherhand, contains the Worker class which inherits from BackgroundService which is an implementation of an IHostedService.

Note that in the constructor method of the Worker class, Worker(), is passed, or shall we say is where an ILogger<T> implementation is injected. This is an example of how .NET Generic Host provides dependency injection for services such as the basic logging provided by .NET Core.

Next the function ExecuteAsync() is called when the hosted service starts. It accepts a CancellationToken which means this long running operation should be cancellable.

ASP.NET Core Web API

Here is the generated code from the ASP.NET Core Web API template:

/* Program.cs */

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
/* WeatherForecastController.cs */

using Microsoft.AspNetCore.Mvc;

namespace ControllerWebAPI1.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

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

        [HttpGet(Name = "GetWeatherForecast")]
        public IEnumerable<WeatherForecast> Get()
        {
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

In the Program.cs file of an ASP.NET Core app such as Web API app,

  1. the WebApplication.CreateBuilder() is called to create an instance of the WebApplicationBuilder class.
  2. Then the following services are added into it’s DI container, the WebApplicationBuilder.Services:
    1. Adds MVC services for controllers via AddControllers()
    2. Configures ApiExplorer using Endpoint’s Metadata via AddEndpointsApiExplorer()
    3. Adds the Swagger generator via AddSwaggerGen(). See Get started with Swashbuckle and ASP.NET Core.
  3. Then you Build() the host, the WebApplication.
  4. Next you configure the HTTP request pipeline or the middleware with the following calls:
    1. Register the Swagger middleware via UseSwagger()
    2. Register the SwaggerUI middleware via UseSwaggerUI()
    3. Adds middleware for redirecting HTTP requests to HTTPS vi UseHttpsRedirection()
    4. Enables authorization capabilities via UseAuthorization()
    5. Adds endpoints to controller actions via MapControllers()
      See ASP.NET Core Middleware for more information of what middleware is and what other built-in middleware components came with ASP.NET Core. Briefly, they replace the HTTP handlers and modules in previous ASP.NET versions and come with Use, Map, or Run extension methods.
  5. And lastly, runs the application via Run() which blocks the calling thread until host shutdown

The Old Startup.cs

You will notice that all the configuration and setup is done in the Program.cs file. Before .NET 6, it used to be in 2 files: Program.cs and Startup.cs and it looks something like this:

/* Program.cs */

public class Program
{
	public static void Main(string[] args)
	{
		CreateWebHostBuilder(args).Build().Run();
	}
	   
	public static IWebHostBuilder CreateWebHostBuilder(string[] args)
	{
		IConfiguration config = Configuration.CreateConfigurationContainer();

		return WebHost.CreateDefaultBuilder(args)
			.UseConfiguration(config)
			.ConfigureLogging(logging =>
			{
				logging.ClearProviders();
				logging.AddConsole();
			})
			.UseStartup<Startup>();
	}
}
/* Startup.cs */

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

	public IConfiguration Configuration { get; }

	// This method gets called by the runtime. Use this method to add services to the container.
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddMvc()
			.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
		// etc.
	}

	// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
	public void Configure(IApplicationBuilder app, IHostingEnvironment env)
	{
		if (env.IsDevelopment())
		{
			app.UseDeveloperExceptionPage();
		}
		else
		{
			app.UseHsts();
		}

		app.UseHttpsRedirection();
		app.UseMvc();
	}
}

You have the Program.cs file where you create the host and call UseStartup<TStartup>() to use a separate file containing the startup code.

In the Startup.cs file, you implement the following 2 methods that are called by the runtime:

  • ConfigureServices(IServiceCollection) - to add services to the container
  • Configure(IApplicationBuilder app, IHostingEnvironment env) - to configure the HTTP request pipeline

As you can see there is more boilerplate code you need to add compared to the new minimal hosting model in .NET 6.

Minimal API

Minimal API is a new type of .NET Core application in .NET 6. You can generate the code from the same ASP.NET Core Web API template but unselecting the checkbox besides Use controllers (uncheck to use minimal APIs). Below is the generated code:

/* Program.cs */

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast");

app.Run();

internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

So it does not have controller code like in the regular Web API but it makes a call to MapGet(). I won’t go over too much on this but feel free to look at Minimal APIs overview for more information. Also Routing basics in ASP.NET Core.

ASP.NET Core Empty

And just to show you how many lines of code is required to get yourself a bare minimum, no content ASP.NET Core web application up and running, you can use the ASP.NET Core Empty template to generate this code for you:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Adding Host to a Console App

As previously mentioned, you need to add the .NET Generic Host to the console app because the generated code does not expose it. You will need to add a NuGet package for Microsoft.Hosting.Extensions before you can use the host. See this video showing you how to do this.


And below is the code to use host in a console app after adding the NuGet package:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = Host.CreateDefaultBuilder(args).Build();

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Host created.");

await host.RunAsync();

And as an added bonus, it also shows you how to get the host’s default logger service to log messages.

What’s Next

There’s still a lot more to cover but this is only part 1. In the next part or so, I like to get more into .NET Generic Host’s features such as dependency injection, logging, and configuration; some of the framework provided sevices; some of ASP.NET Core’s built-in HTTP middleware like authentication and authorization; and how to create a custom HTTP middleware.

Additional References