ASP.NET Core Web API Fundamentals
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.
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.
- Environment variables prefixed with
- 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.
- A service is a reusable component that provides application functionality. For example, services could be:
- In
Configure
method, the application’s request handling pipeline is defined, as a series of middleware components.
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.
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.
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:
- High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
- 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.
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:
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.
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 😁.