Dotting Your i's and Crossing Your t's: .NET Configuration
Tags: dotnet, dotnetcore, configuration, beginners
Published: 2023-01-26 08:39:29

Configuring .NET apps can be confusing with all the different options available. We have different types of apps like Web APIs, Background Service and Console app etc. which can run on different .NET versions or environments and setting them up with correct configurations can be overwhelming.

I want to share what I learned that helped me understand how all the different configurations fit together and make sense of it all. 

Before we start let's first understand .NET project SDK which is a base for different types of configuration.

### .NET project SDK
.NET Core, .NET 5 and later projects are associated with a SDK (Software Development Kit). It is responsible for compiling, packing and publishing code. SDK contains .NET CLI, runtime and libraries.

We can target SDK in project file to configure different types of .NET apps.

```
<Project Sdk="Microsoft.NET.Sdk">
  ...
</Project>
```

#### Available SDKs
|ID|Description|
| ------------- |:-------------:| -----:|
| `Microsoft.NET.Sdk` | The .NET SDK. This is the base SDK for all other SDKs |
| `Microsoft.NET.Sdk.Web` | The .NET [Web SDK](https://learn.microsoft.com/en-us/aspnet/core/razor-pages/web-sdk) | 
| `Microsoft.NET.Sdk.BlazorWebAssembly` | The .NET [Blazor WebAssembly](https://learn.microsoft.com/en-us/aspnet/core/blazor#blazor-webassembly) SDK |
| `Microsoft.NET.Sdk.Razor` | The .NET [Razor SDK](https://learn.microsoft.com/en-us/aspnet/core/razor-pages/sdk) | 
| `Microsoft.NET.Sdk.Worker` | The .NET [Worker Service](https://learn.microsoft.com/en-us/dotnet/core/extensions/workers) SDK | 
| `Microsoft.NET.Sdk.WindowsDesktop` | The .NET [Desktop SDK](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props-desktop) |

## ASP.NET Core apps
ASP.NET Core is composed of several building blocks. These blocks can be configured to suit the requirements and shape the overall configuration of the application. We are going to look at some of the important blocks.
- Host
- Dependency Injection
- Middlewares
- Configuration
- Environments
- Logging
- HttpContext
- Routing
- Server

### Host
The Host, also referred as Host builder or builder, is the first step in configuration. It is used to configure app and its services. Host contains all resources of an app:
- An HTTP server implementation
- Middleware components
- Logging
- Dependency injection (DI) services
- Configuration

There are three types of hosts you can choose which depends on the .NET project SDK:
- **ASP.NET Core Web Host**
    - Exists only for backward compatibility
    - Supports .NET Core 2.x Apps
    - Uses `WebHost.CreateDefaultBuilder()` to create builder
- **.NET Generic Host**
    - Available in all .NET SDKs
    - Supports ASP.NET Core 3.x and .NET 5 Apps
    - Uses `Host.CreateDefaultBuilder()` to create builder
    - More info: [.NET Generic Host](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?view=aspnetcore-7.0#set-up-a-host)
- **.NET WebApplication Host** also known as Minimal Host
    - Simplified version of .NET Generic Host
    - It provides some of the default configuration and hence significantly reduces the number of files and lines of code to configure an app. 
    - Available only in `Microsoft.NET.Sdk.Web`
    - Supports .NET 6 Apps 
    - Uses `WebApplication.CreateBuilder()` to create builder
    - More info: [.NET Web Host](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?view=aspnetcore-7.0)

The following example configures apps and services using .NET Generic host:

```
public class Program
{
    public static void Main(string[] args)
    {
        // create host and run app
        CreateHostBuilder(args)
            .ConfigureHostConfiguration(config => config.SetBasePath(Directory.GetCurrentDirectory()))
            .Build()
            .Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

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

    // configure services
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped(typeof(IGitHubService), typeof(GitHubService));
        services.AddScoped(typeof(ISlackService), typeof(SlackService));
    }

    // configure app
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();
    }
}
```

Same configuration but with WebApplication host:
```
// create host
var builder = WebApplication.CreateBuilder(args);

// configure services
builder.Services.AddScoped(typeof(IGitHubService), typeof(GitHubService));
builder.Services.AddScoped(typeof(ISlackService), typeof(SlackService))

// configure app
var app = builder.Build();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

// runs app after configuration
app.RunAsync();
```

### Dependency Injection (services)
ASP.NET Core includes dependency injection (DI) that makes configured services available throughout an app. Services are added to the DI container using WebApplicationBuilder.Services (builder.Services). When the builder is instantiated, many [framework-provided services](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0#framework-provided-services) are added by default.

Example
```
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddScoped(typeof(IContactRepository), typeof(ContactRepository));

var app = builder.Build();
```
As services are add to DI container, we can resolve these services using constructor.
```
public class Contact
{
    public readonly IContactRepository _contactRepository;

    public Contact(IContactRepository contactRepository)
    {
        _contactRepository = contactRepository;
    }

    public Contact Get(int id)
        => _contactRepository.Get(id);
}
```
### Middleware
In simple terms, middleware is a set of tools that connects different parts of an application and allows them to work together. When the application gets a request, the middleware receives it first and directs it to the right place in the application.

In the configuration, we can add series of middleware in request pipeline. These middleware are chained together. In even of an incoming request, each middleware in sequence - 
- Chooses whether to pass the request to the next middleware.
- It can perform its operation before and after the next middleware.

![How middleware works](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/index/_static/request-delegate-pipeline.png?view=aspnetcore-7.0)

By convention, a middleware is invoked by an extension method starts with "Use" keyword. Sequence is the order middleware are defined. "Run" or "RunAsync" method in the last terminates the request.
```
// create host
var builder = WebApplication.CreateBuilder(args);

// configure services
builder.Services.AddScoped(typeof(IContactRepository), typeof(ContactRepository));

// configure app
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseHttpsRedirection();
app.UseAuthorization();

// custom middleware
app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

// terminates the request
app.Run();
```

From here on things are easy if you understand SDKs, Hosts, DI & Middleware. Let's check rest of the building blocks quickly -

### Configuration
ASP.NET Core apps provides configuration framework. It get settings in key-value pairs. It has many built-in configuration providers which gets settings/configurations from different sources:
- Settings files, such as `appsettings.json`
- Environment variables
- Azure Key Vault
- Azure App Configuration
- Command-line arguments
- Custom providers, installed or created
- Directory files
- In-memory .NET objects

You can use host/builder to configuration from multiple sources.

```
var builder = WebApplication.CreateBuilder(args);

// Services
var settings = builder.Configuration.GetSection("Settings");
builder.Services.Configure<Settings>(settings);

var app = builder.Build();

// Configure
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();


// Terminates request
await app.RunAsync();
``` 

### Environments
Set the `ASPNETCORE_ENVIRONMENT` environment variable to your environment name and access it using Host Builder. By default `Development`, `Staging` and `Production` values are supported .NET configuration.

ASP.NET Core reads that environment variable at app startup and stores the value in an `IWebHostEnvironment` implementation. This implementation is available anywhere in an app via dependency injection (DI).
```
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.Run();
```

### Logging
ASP.NET Core supports variety of [built-in](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-7.0#bilp) and [third-party](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-7.0#third-party-logging-providers) logging providers:
- Console
- Debug
- Event Tracing on Windows
- Windows Event Log
- TraceSource
- Azure App Service
- Azure Application Insights

When you create a building using `WebApplication.CreateBuilder` it adds Console, Debug, EventSource and EventLog by default. You can reconfigure logging providers using Host builder.

```
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddConsole();

var app = builder.Build();
app.Run();
```

### Final thoughts
If we rethink the structure of the ASP.NET core building blocks it looks something like this -
```
- .NET project SDK
  - Host (builder)
    - Dependency Injection (builder.Services)
    - Configuration (builder.Configuration)
    - Logging (builder.Logging)
    - App (builder.Build())
      - Middlewares (app.Use{ExtensionMethod})
      - Environments (app.Environment)
```

I hope this helps to clear confusion. Thanks for reading!

--- 
#### References
- [ASP.NET Core fundamentals overview](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/?view=aspnetcore-7.0&tabs=windows)
- [.NET project SDKs](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview)
- [Comparing WebApplicationBuilder to the Generic Host](https://andrewlock.net/exploring-dotnet-6-part-2-comparing-webapplicationbuilder-to-the-generic-host/)