THE PROJECT FILE .CSPROJ
Let’s analyze the project file. This content is used to instruct the Build platform on how our application should be compiled and published. TargetFramework identifies the version of .NET Core, in our case version eight. The moment version nine comes out we will simply update this value. There is no reference to any source files, i.e., files having the extension .cs as this is not necessary. It will be the Build platform that will take care of searching and compiling all the sources of the project, it is no longer necessary to specify all of them, if anything you indicate the files that should be excluded from the compilation. Let’s give it a try and exclude the Startup.cs file.
We compile and get an error as the file is not found in the Program.cs class
ItemGroup groups all references to NuGet packages installed in the project. At the moment we have not added any. Let’s try adding the SQLite dependency, database that we will use in the project. We go to NuGet.org and type in SQLite. As you can see, the dependency was added with the following command: dotnet add package Microsoft.Data.Sqlite.Core –version 8.0.1
THE BIN AND OBJ DIRECTORY AND THE PROJECT COMPILATION
The Build platform is part of the SDK, we run it on the development server not on the production server where the runtime is to be installed. Let’s look at the steps of compiling a project triggered by the dotnet build command.
With the compilation process we get two new directories, bin and obj. obj is a kind of temporary directory where files are produced that will be used for compilation. The output directory, where the compilation phase files are, is the bin. MyCourse.dll is the assembly that contains all the compiled code of our application, the MyCourse.pdb file is very useful when debugging the code. It contains the mapping between lines of C# code and the instructions contained in MyCourse.dll. The bin and obj directories we can delete at any time as they are recreated as is by the dotnet build command.
Let’s look at the execution phase of the application.
An additional compilation step is required to transform the IL code into machine code for that particular processor and architecture in use. This compilation step is totally transparent and automatic, and we do not have to do anything. We can ask why Microsoft introduced this new compilation step, that is, C# code could not be compiled directly into machine language? The reason is that the dlls containing IL code allow us to make our application easily portable, suppose we want to sell it, because of the presence of the assemblies we can sell it to anyone regardless of what platform they want to use; therefore, whoever buys the application can place it on Windows or Linux without the need for further compilation.
PREPARE THE WEB HOST FROM THE PROGRAM CLASS
As of .NET 6.0 we can do without the Startup.cs class, in fact Microsoft has introduced a new approach to application configuration. This new approach is called Minimal Hosting Model, and so we could get by with just the Program.cs file. Beware, however, this approach is optional, and we will continue to use the old configuration for the time being. Let’s look at the minimal configuration of the class Startup.cs.
The ConfigureService and Configure methods are used to configure two aspects of the application, from the Configure method we decide which Middleware to activate, Middleware are modules executed at each user request, while ConfigureService is used to activate Dependencies Injection and services. After the Build phase, the application is ready to run. When executed the Program.cs class comes into play, which allows us to receive web requests from users, contains the entry point of our application, the Main method is the first one that is called by the .NET Core runtime.
The first thing that is done in the Main method is the creation of a Web Host, which contains everything that is in direct contact with the outside world. A component that can receive http requests from the user is called a web server. Microsoft has made Kestrel that lives within the dotnet process, this is a big difference from other technologies such as PHP, Apache, Nginx that live on separate processes. Kestrel’s job is to collect http requests, transform them a bit, and represent them with an HttpContext object.
There are also other components within the Web Host, such as the Logging component which is useful if we want to write diagnostic values, another component is configuration, we should be able to send configuration parameters to the application though.
All of these components are modular, i.e., they can be changed to our liking, even the Web Server can be changed with HTTP.sys, which, however, has the drawback of working only on Windows and the merit of authenticating users with their domain accounts. One important thing to say is that .NET Core is self contained, if we want we can also install the runtime and deploy it on a USB stick. With a double click the application will be put into execution.
CONFIGURE LAUNCH PARAMETERS WITH LAUNCHSETTINGS.JSON
When we start the application with the dotnet run command Kestrel will be listening on these two end points.
These end points are set in the launchSettings.json file located in the properties directory. The environment name is very important when, as we shall see, we need to differentiate behavior between development and production. For example, if an error occurs in Development, we want to have all the information about the nature of the error, whereas in the Production environment it is good not to release certain information, such as the code we are developing. In this case a courtesy message will be displayed for the user while we will write the detailed information of the error in a log file.
USE MIDDLEWARE IN THE STARTUP CLASS
All these behavioral differences between development and production are managed through Middleware. The application code that receives the HttpContext object, is structured in Middleware that will read this object, modify it if necessary, and pass it on to the next Middleware. They can do many things, access a database, write a log file etc. These Middleware are either produced by Microsoft, or we can download them from NuGet or produce them ourselves. It is a completely modular mechanism, and we will decide how many and which components to use.
In the Configure method of the Startup.cs class, we are going to enter the whole set of Middleware we need in a given environment.
The first Middleware that we added when we are in development is the UseDeveloperExceptionPage() which takes care of creating a screen in case of an error. The order of addition is very important, having put this Middleware first it can catch any errors that may arise in subsequent Middleware and capture a detailed error screen. The next Middleware has no name, it captures the HttpContext object and writes in the output response. Let’s see how this all works. Strings have a toUpper() method to convert them to uppercase, and suppose we want the name variable to be converted. If we run the application, an unhandled error caught by the previous Middleware occurs.
Let us say that Middleware was very helpful in unearthing the error; however, in production this should not be done as we are disclosing too much information about our code. If we try to change the string in the launchSettings.json file from Development to Production, Middleware will not be activated and what we get is a trivial http 500 error.
Let us see the previous situation graphically, that is, the one in which Middleware intervenes and handles the error.
The other case is as follows:
MANAGE STATIC FILES AND THE WWWROOT DIRECTORY
Static files are picked up by the Kestrel web server as-is from the disk and served to the user. All static files must be placed inside the wwwroot folder. If we try to insert an image in wwwroot and make a request we find that it is not served. This is because we did not configure the Middleware.
Static File Middleware analyzing HttpContext realized that the request could be served by itself; therefore, it immediately returned the content to the client.
SECTION SUMMARY
An ASP.NET Core application is programmed with a language such as C#, which is a “compiled” language. It means that before it can be executed, the code we have written in the .cs files must go through a compilation phase that will turn it into IL (Intermediate Language), which is a lower-level byte code language and therefore closer to CPU-executable code. This compilation process serves to bring together the needs of developers, who want to work with an expressive language such as C#, and the needs of machines, which are able to work all the more efficiently the simpler the instructions to be processed.
A .dll assembly is portable on any of the platforms supported by .NET Core. As the application runs, the assemblies are loaded into memory and, as needed, go through a new compilation step by the JIT compiler (Just-In-Time) that “translates” Intermediate Language instructions in real time into code directly executable by the specific CPU on which it runs.
THE BUILD PLATFORM
With .NET Core SDK, a build platform is provided in which the MSBuild component that orchestrates the compilation and publication of the project is located. It can handle files of different types:
- Code files with a .cs extension are passed to the C# language compiler called Roslyn and the resulting Intermediate Language is placed within one or more assemblies, i.e., .dll files that we will find within the bin directory;
- .json files are also produced that tell .NET Core Runtime what version of .NET Core is used and what other libraries the ASP.NET Core application depends on;
- When publishing, static files such as the contents of the wwwroot directory or the web.config file are also copied, which facilitates production deployment behind the back of the IIS webserver that will act as a reverse proxy (we will return to this in the future).
To perform the compilation we have several commands at our disposal:
- dotnet build performs the compilation but does not copy the static files;
- dotnet publish performs both compilation and also copies static files. This is the command to execute when we want to prepare all the contents in a directory that we are then going to copy to the production server via FTP or other technique;
- dotnet run implicitly triggers the build (if needed), since it is a requirement to be able to run the application.
When an application has been built, we can run the dotnet command MyCourse.dll to start it, where MyCourse.dll is the path to the assembly that was created as a result of the build. This is the command to run on the production server, since we have .NET Core Runtime installed there, which does NOT contain the build platform and therefore commands such as dotnet build and dotnet run would not work.
CUSTOMIZE THE BUILD
The .csproj file contains instructions for the build platform, which will then be able to compile the project correctly. Some of the information it reports is:
- The so-called project SDK that tells the build platform how to compile an ASP.NET Core application, which has different peculiarities from other types of applications;
- The target framework, which is the specific version of .NET Core that we intend to use;
- A list of static files to include when publishing, or code files to exclude from compilation;
- A list of references to NuGet packages on which our application depends.
THE WEB HOST
The Program class, contained in the Program.cs file, has the Main method, which is also called theentry point of the application because it is the first to be invoked when it starts. From the Main method, the web host is prepared, which is the set of essential components that serve the application to relate to the outside world. For example, one such component is the Kestrel web server, which allows the application to receive HTTP requests. Web host components can be configured or replaced as desired, depending on the specific needs of the application we need to implement.
For most scenarios, the default web host configuration is more than sufficient. However, if we want to peek at how the default web host is built, we can consult the source code of ASP.NET Core in its GitHub repository. Here is a link to the specific spot where it is prepared:
It can be seen that within it are configured:
- The Kestrel web server and integration with IIS;
- The definition of configuration sources (which we will see later);
- Console logging;
- The service provider (which we will see later).
I MIDDLEWARE
The Startup class, defined within the Startup.cs file, is also involved during the construction of the web host. This class has a Configure method from which we indicate which middleware to use in the application.
Middleware are components that provide their own specific functionality and are called upon to handle incoming HTTP requests. They are arranged one after another in an orderly series that is also called a “pipeline.” Each middleware has the power to:
- Interact with the HttpContext object representing the current execution context. This object can be used by the middleware to read the information passed with the request, to add arbitrary information to it, or to rewrite it altogether;
- When it has finished its work, the middleware can either cede the handling of the request to the next middleware, or prevent it from doing so. Thus, it is not necessarily the case that every request is handled by all middleware in the series;
- Produce a response for the client or rewrite or supplement the response previously produced by another middleware.
The order in which we use the middleware is important: the first middleware in the series will be the first to handle the request, but also the last to be able to act on the response that is sent to the user.
Each middleware offers specific functionality such as:
- Intercept errors (e.g., by invoking app.UseDeveloperExceptionPage());
- Serve static files (by invoking app.UseStaticFiles());
- Logging requests;
- Authenticate and authorize the user;
- Do routing or rewrite the request url;
- Compress the responses with the Gzip algorithm so that they require less bandwidth;
- Adjust access to the application based on the source IP address;
- … and so on.
As a general rule, we can say that any application functionality must be added with middleware.
STATIC CONTENT AND THE WWWROOT DIRECTORY
If we want the application to be able to provide static files, we will need to add the appropriate middleware by invoking app.UseStaticFiles() from the Configure method of the Startup class. Any static file, such as a jpg image, a .css stylesheet or a .js code file, must be located within wwwroot or one of its subdirectories. In fact, anything located outside wwwroot will not be viewable in the browser or otherwise downloadable via web request. Therefore, any confidential files, such as a Sqlite database or log file will have to be placed outside wwwroot.
INTERCEPT ERRORS
Similarly, if we want to intercept errors and display them in an information page, we will also need to add appropriate middleware with app.UseDeveloperExceptionPage(). This middleware is recommended only in the development environment, since it discloses information about our source code. For this reason, it is important to add it conditionally through an if in the Configure method of the Startup class.
- if(env.IsDevelopment())
- {
- UseDeveloperExceptionPage();
- }
LAUNCHSETTINGS.JSON
The name of the environment can be controlled from the Properties/launchSettings.json file located in the project. Setting the environment name appropriately can allow us to configure the application and its middleware slightly differently to accommodate various needs. The name of the environment can be set arbitrarily, but usually the ones used are as follows:
- Development we use it while we are developing the application on our PC or Mac;
- Staging we use it during a testing phase that takes place just before going into production. The application is located on a server as close as possible to the production server to test whether it works well under conditions other than the PC in which it was developed;
- Production we use it when the application is in production and running on the server, so that it is accessible by its users.
Set application startup addresses
By default, an ASP.NET Core application can be reached from the addresses http://localhost:5000 and https://localhost:5001 but we can redefine these addresses from the Properties/launchSettings.json file.
Leave A Comment