.NET Nakama

ASP.NET Core Web API Fundamentals

November 04, 2020 (~11 Minute Read)
BASICS ASP.NET CORE PROGRAM STRUCTURE DEPENDENCY INJECTION

Introduction

In this article, our goal is to understand ASP.NET Core fundamentals, including the program structure, Dependency Injection (DI), the request pipeline, middleware, and two related principles, the Dependency Inversion Principle (DIP) and the Inversion of Control (IoC) principle.

ASP.NET Core Fundamentals

Let’s start by investigating the basic generated files by the “ASP.NET Core Web Application” template. You can follow the steps described in .NET Nakama (2020, October) or download the source code from GitHub.

The Program class (.NET Generic Host)

ASP.NET Core applications configure and launch a Generic Host. The host is responsible for the application startup and lifetime management. At a minimum, the host configures a server and a request processing pipeline. The host can also set up logging, dependency injection, and configuration.

The “ASP.NET Core Web Application” template generates the following code to create a Generic Host, which is typically configured, built, and run, by code in the Program class (Program.cs). The Main method calls a CreateHostBuilder method to create and configure a builder object (by calling the CreateDefaultBuilder and ConfigureWebHostDefaults methods, which are shown and described below).

The UseStartup<T>() is used to configure which class will be used to start our application code. In the following section, we will see what a startup class contains.

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

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

CreateDefaultBuilder

As shown in ASP.NET documentation, the CreateDefaultBuilder method performs the following by default:

  • Sets the content root to the path returned by GetCurrentDirectory.
  • Loads host configuration from:
    • Environment variables prefixed with DOTNET_.
    • Command-line arguments.
  • Loads app configuration from:
    • appsettings.json.
    • appsettings.{Environment}.json.
    • Secret Manager when the app runs in the Development environment.
    • Environment variables.
    • Command-line arguments.
  • Adds the following logging providers:
    • Console
    • Debug
    • EventSource
    • EventLog (only when running on Windows)
  • Enables scope validation and dependency validation when the environment is Development.

ConfigureWebHostDefaults

As shown in ASP.NET documentation, ConfigureWebHostDefaults method performs the following by default:

  • Loads host configuration from environment variables prefixed with ASPNETCORE_.
  • Sets Kestrel server as the web server and configures it using the app’s hosting configuration providers.
  • Adds Host Filtering middleware.
  • Adds Forwarded Headers middleware (if ASPNETCORE_FORWARDEDHEADERS_ENABLED equals true).
  • Enables IIS integration.

The Startup class

The Startup class configures a) services and b) the application’s request pipeline. It is named as Startup by convention, meaning that we can give it any name, as long as we specify it to the UseStartup <OurStartupClassName> in the Program class.

Figure 1 shows the generated code of the Startup class, which contains two methods ConfigureServices and Configure:

  • In ConfigureServices method, we can configure the services required by the application.
    • A service is a reusable component that provides application functionality. For example, services could be:
      • Data Persistence Services, which provide a way (an implementation) to use the application’s database.
      • Application Services, which provide implementations for the specific application needs (e.g. sending emails, third-party communication services, etc.)
      • Custom Configuration Services, which provide a way to have configurable options.
    • Services that are registered in the ConfigureServices method are consumed (used) across the application via Dependency Injection (DI). We will see more about DI in the following section.
  • In Configure method, the application’s request handling pipeline is defined, as a series of middleware components.
The generated code of the Startup class (ConfigureServices and Configure methods).
Figure 1. - The generated code of the Startup class (ConfigureServices and Configure methods).

Basics of Request Pipeline in ASP.NET Core

We have already seen that in the Configure method, the application’s request handling pipeline is defined as a series of middleware components.

From Figure 1, we can see that in the Startup.Configure method there are several app.Use{Something}. Each one of these is a middleware component attached to the request pipeline series (in a sequence). Meaning that, the order that the middleware components are added in the Startup.Configure method, defines the order in which the middleware components are invoked on requests and the reverse order for the response. The order is critical for security, performance, and functionality. For that reason, we can understand why we have to call app.UseAuthorization before app.UseEndpoints.

The ASP.NET Core request pipeline consists of a sequence of request delegates, called one after the other (Figure 2). Each component:

  • Chooses whether to pass the request to the next component in the pipeline.
  • Can perform work before and after the next component in the pipeline.

For example, an incoming HTTP request it is received by the first middleware component of the pipeline (e.g. Middleware1) to perform its logic and then, it can call the next() component of the pipeline (e.g. Middleware2). When there are no more next() components to call, the components are invoked in a reverse order to perform an additional logic and finally create the response.

The concept of the ASP.NET Core request pipeline
Figure 2. - The concept of the ASP.NET Core request pipeline (Source).

ASP.NET Core provides several built-in middleware components for the most common use-cases, such are the following. But there are scenarios where we would like to write our own custom middleware. We would see how to do that in a future article.

Middleware Description Order
Authentication Provides authentication support. Before HttpContext.User is needed. Terminal for OAuth callbacks.
Authorization Provides authorization support. Immediately after the Authentication Middleware.
CORS Configures Cross-Origin Resource Sharing. Before components that use CORS.
Forwarded Headers Forwards proxied headers onto the current request. Before components that consume the updated fields. Examples: scheme, host, client IP, method.
Health Check Checks the health of an ASP.NET Core app and its dependencies, such as checking database availability. Terminal if a request matches a health check endpoint.
Session Provides support for managing user sessions. Before components that require Session.

Basics of Dependency Injection in ASP.NET Core

In general, dependency or dependent means relying on something else. A dependency in Object-oriented programming (OOP) is when an object (i.e. an instance of a class) is needed by another class to do its work. In other words, when class A uses a functionality of class B, then we can say that (Figure 3):

  • Class A is dependent on class B, or
  • Class B is a dependency of class A.
Class A is dependent (e.g. needs some method) on class B.
Figure 3. - Class A is dependent (e.g. needs some method) on class B.

The Dependency Inversion Principle (DIP) is the fifth principle of the SOLID theory, which was introduced by Robert C. Martin (a.k.a. Uncle Bob) in 2000. DIP states that:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
  2. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

To simplify a little this principle, we can say that a class should depend on abstractions and not from concrete classes, as presented in the following figure.

Class A is dependent to an abstraction (Interface B) and not directly to Class B.
Figure 4. - Class A is dependent to an abstraction (Interface B) and not directly to Class B.

Until now, we have used an abstraction to make our classes (A & B) loosely coupled. Loose coupling is a design goal to reduce the dependencies between components of a system, to reduce the risk that changes in one component will require changes in any other components. Thus, provides us with the flexibility to make easier modifications, add features, fix bugs, etc.

The object instantiation of class B (that is the dependency, e.g. InterfaceB foo = new ClassB()) must be performed outside of class A (not be inside it). Inversion of Control (IoC) principle suggests to invert the dependency creation control from class A to another class.

Dependency Injection (DI) is software design pattern used to implement IoC principle between classes and their dependencies. ASP.NET Core supports DI by providing a built-in service container, IServiceProvider. Services are typically registered in the application’s Startup.ConfigureServices method. You can think the “registering” as the mapping between the service interface and the implementation that must be used. For example, in order to use the class B implementation for InterfaceB we can register it as follows:

public void ConfigureServices(IServiceCollection services)
{
  services.AddControllers();

  // For InterfaceB use the implementation of class B

  services.AddTransient<InterfaceB, B>();
}

The service (dependency) can be injected into the constructor of the class where it’s used (e.g. class A). The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it’s no longer needed. Figure 5 shows an example of DI as a parameter in the constructor. For more details and code examples about IoC and Dependency Injection click here.

An example of DI as a parameter in the constructor.
Figure 5. - An example of DI as a parameter in the constructor.

Services can be registered with one of the following lifetime: Transient, Scoped or Singleton. The lifetime of a service is related to when a service-instance must be created and disposed.

  • Transient: Transient lifetime services (AddTransient) are created each time they’re requested from the service container. This lifetime works best for lightweight, stateless services.
  • Scoped: Scoped lifetime services (AddScoped) are created once per client request (connection).
  • Singleton: Singleton lifetime services (AddSingleton) are created the first time they’re requested (or when Startup.ConfigureServices is run and an instance is specified with the service registration). Every subsequent request uses the same instance.

Summary

In this article, we were focused on understanding the ASP.NET Core fundamentals. We have investigated the generated program structure to understand how the program starts and understand the responsibilities of the default builder and host. This will help us to make changes of the defaults.

In addition, we have investigated the Startup class which configures a) services and b) the application’s request pipeline. The application’s request handling pipeline is defined, as a series of middleware components. The order that the middleware components are added is critical for security, performance, and functionality. Imaging a problematic scenario in which we return the response and afterwards we check if the user is authorized 😱. I recommend you to check the provided ASP.NET Core built-in middleware components, which can be used for the most common use-cases.

An important part of the ASP.NET Core is that we can register services (reusable components) that provide application functionalities. These services are consumed across the application via Dependency Injection (DI). In addition, we have studied two related principles, the Dependency Inversion Principle (DIP) and the Inversion of Control (IoC) principle.

Finally, we saw that using ASP.NET Core to build Web APIs we can leverage DI (and the studied principles) to build applications that are loosely coupled, more testable, modular, and maintainable.

If you liked this article (or not), do not hesitate to leave comments, questions, suggestions, complaints, or just say Hi in the section below. Don't be a stranger 😉!

Dont't forget to follow my feed and be a .NET Nakama. Have a nice day 😁.