Why did Microsoft create ASP NET Core

ASP.NET Core: Getting started with ASP.NET Core 2.0

  • 17 minutes to read

September 2017

Volume 32, number 9

Posted by Mike Rousos | September 2017

ASP.NET Core makes it easy to build fast, portable, cross-platform web applications. This article takes you step-by-step through developing a simple ASP.NET Core website and shows you the role each file plays in the project. It also explains key ASP.NET Core concepts. Particular attention is paid to the changes in ASP.NET Core 2.0, so that users who are familiar with ASP.NET Core 1.0 and 1.1 can make the transition to version 2.0 easier.

Create an ASP.NET Core project

ASP.NET Core projects can be created from templates using Visual Studio or the .NET Core command-line interface (.NET CLI). Visual Studio 2017 offers great .NET Core development capabilities with a world-class debugger, Docker integration, and loads of other features. However, I will be using the .NET CLI and Visual Studio Code in this walkthrough because it will be followed by users who want to use a Mac or Linux development machine.

The "dotnet new" command is used to create new .NET Core projects. If “dotnet new” is executed without any further arguments, all available project templates are listed. illustration 1 shows this. If you are familiar with previous versions of the .NET CLI, you will notice that there are some new templates available in version 2.0.

Figure 1: New .NET Core project templates

React.js Angular and SPA Templates: These templates create an ASP.NET Core application that is front-end to a single-page application (using Angular 4 or React.js). The templates include the front-end and back-end applications, as well as a webpack configuration to build the front-end (and .csproj changes to trigger the webpack build every time the ASP.NET Core project is built).

ASP.NET Core Web App with Razor Pages: Razor Pages are a new ASP.NET Core 2.0 feature that allows you to create pages that can process requests directly (without the need for a controller). They are an excellent option in scenarios that use a page-based programming model.

In this walkthrough, let's start with the new razor page template by running “dotnet new razor”. Once the project is created you should be able to run it by typing "dotnet run". In previous versions of .NET Core, it would have been necessary to run dotnet restore beforehand in order to install the required NuGet packages. Starting with .NET Core 2.0, the restore command is now automatically executed by the CLI commands that depend on it. Go ahead and test the template website by running dotnet run and navigating to the url the app is listening on (likely http: // localhost: 5000). The web app should (as in Figure 2) are rendered.

Figure 2: Simple ASP.NET Core app currently running

Congratulations on the successful launch of your first ASP.NET Core 2.0 app! Now that your simple web app is running, let's take a closer look at the contents of the project to better understand how ASP.NET works.

Dependencies, sources and resources

The first file I want to examine is the project file itself: the .csproj file. This file tells MSBuild how to build the project (which packages it depends on, which target version of .NET Core to use, etc.). If you've looked at the .csproj file in the past, you'll notice that this project file is much smaller. It took a lot of effort to shorten .csproj files and make them easier for people to read. One notable change that has helped shrink the project file is that source files no longer need to be explicitly listed. Instead, the .NET Core SDK automatically compiles all CS files next to the project file or in directories under the directory of the .csproj file. Similarly, each .resx file discovered is embedded as a resource. If you do not want to compile all of these CS files, you can remove them from the Compile ItemGroup or disable standard compilation items entirely by setting the EnableDefaultCompileItems property to FALSE.

The .NET version with which the app should run is indicated by the element. This is set to "netcoreapp2.0" in order to use the new ASP.NET Core 2.0 features and the much larger .NET Core 2.0 interface.

The element in the middle of the .csproj file indicates a NuGet package on which the project depends. You will notice that in ASP.NET Core 2.0 only one metapackage ("Microsoft.AspNetCore.All") is now included. This package contains all the other Microsoft.AspNetCore packages in a short reference and results in ASP.NET Core 2.0 project files being much smaller than project files from previous versions. Additional NuGet dependencies can be added by adding more elements, using the NuGet Package Management UI in Visual Studio, or using the dotnet add command in the .NET CLI.

If you used a SPA template ("angular", "react" or "reactredux"), custom targets would also be defined in the ".csproj" file to ensure that Webpack is executed when the project is created.

Create and run the web host

“Program.cs” represents the entry point of the application. ASP.NET Core apps are console applications and therefore (like all console apps) have a “Main” method that is started when the app is run.

The content of the main methods of the ASP.NET Core 2.0 templates is quite simple: You create an IWebHost object and call "Run" for this.

If you've used ASP.NET Core 1.0 before, you'll find that this file is a bit simpler than the program.cs file from these older templates. The reason for this is the new WebHost.CreateDefaultBuilder method. In previous versions, the ASP.NET Core Main method configured a WebHostBuilder to create the IWebHost instance. This configuration included steps such as specifying a web server, setting the content root, and enabling IIS integration.

The new CreateDefaultBuilder method simplifies everything by creating a ready-to-use IWebHost for which most of the general configuration has already been done. In addition to specifying the items previously listed, CreateDefaultBuilder does some setup tasks that were previously handled in Startup.cs (setting up configuration information and registering standard logging providers). Since ASP.NET Core is open source, if you are interested you can see all the details of how CreateDefaultBuilder works by examining the source on GitHub (bit.ly/2uR1Dar).

Let's take a quick look at the most important calls that are made in “CreateDefaultBuilder” and their purpose. Even if these calls (by CreateDefaultBuilder) are all executed for you, it doesn't hurt to know what is going on behind the scenes.

UseKestrel indicates that your application should use Kestrel, a libuv-based, cross-platform web server. Another option here would be to use “HttpSys” as a web server (“UseHttpSys”). "HttpSys" is only supported under Windows (Windows 7/2008 R2 or higher), but has the advantage that Windows authentication is possible and running with a direct Internet connection is secure (on the other hand, Kestrel should be behind a reverse proxy such as IIS, Nginx or Apache when requests are received from the Internet).

UseContentRoot specifies the root directory for the application where ASP.NET Core can find website-wide content such as configuration files. Note that this is different from the web root (from which static files are processed) even though the web root is based on the content root by default ("[ContentRoot] / wwwroot").

ConfigureAppConfiguration creates the configuration object that the app uses to read settings at runtime. When called through CreateDefaultBuilder, application configuration settings are read from an appsettings.json file, an environment-specific JSON file (if any), environment variables, and command line arguments. If the call is made in a development environment, secret user keys are also used. This method is new in ASP.NET Core 2.0. I'll explain them in more detail below.

ConfigureLogging sets up logging for the application. When called through CreateDefaultBuilder, console and debug logging providers are added. Like “ConfigureAppConfiguration”, this method is also new and will be discussed in more detail later.

"UseIISIntegration" configures the application to run in IIS. Note that "UseKestrel" is still required. IIS is acting as the reverse proxy, and Kestrel continues to be used as the host. In addition, "UseIISIntegration" would have no effect if the app was not running behind IIS. The call is therefore safe even when the app is running in non-IIS scenarios.

In many cases the default configuration provided by CreateDefaultBuilder is sufficient. Apart from this call, you only have to specify the start class for your application by calling “UseStartup ”. "T" is the start type.

If “CreateDefaultBuilder” does not meet the requirements of your scenario, you can of course adjust how the “IWebHost” is created. If only minor changes are required, you can call CreateDefaultBuilder and then modify the returned WebHostBuilder (possibly by calling ConfigureAppConfiguration again to add more configuration sources). If more extensive changes to “IWebHost” are required, you can completely skip the call to “CreateDefaultBuilder” and simply create a “WebHostBuilder” yourself, as with ASP.NET Core 1.0 or 1.1. Even if you use this procedure, you can still use the new ConfigureAppConfiguration and ConfigureLogging methods. For more details on web host configuration, see bit.ly/2uuSwwM.

ASP.NET Core environments

Some of the actions that CreateDefaultBuilder takes depend on the environment in which your ASP.NET Core application is running. The concept of the environment is not new in version 2.0, but it should be explained briefly because it is used frequently.

In ASP.NET Core, the environment in which an application runs is specified by the ASPNETCORE_ENVIRONMENT environment variable. You can set this variable to any value you want. Usually, however, the values ​​"Development", "Staging" and "Production" are used. So if you set the ASPNETCORE_ENVIRONMENT environment variable to Development before calling dotnet run (or you set this environment variable in a launchSettings.json file), your app will run in development mode (and not in production mode, which is Default setting with no defined variables). This value is used by various ASP.NET Core features (I'll cover this below when describing configuration and logging) to change runtime behavior. It can be accessed in your own code using the IHostingEnvironment service. For more information on ASP.NET Core environments, see the ASP.NET Core documentation (bit.ly/2eICDMF).

ASP.NET Core configuration

ASP.NET Core uses the IConfiguration interface of the Microsoft.Extensions.Configuration package to provide runtime configuration settings. As already mentioned, "CreateDefaultBuilder" reads settings from JSON files and environment variables. However, the configuration system is extensible and can read configuration information from a variety of providers (JSON files, XML files, INI files, environment variables, Azure Key Vault, etc.).

When working with IConfiguration and IConfigurationBuilder objects, remember that the order in which the providers are added is important. Providers added later can overwrite the settings of previously added providers. Therefore, you should add general base providers first and environment-specific providers later, which may override some of the settings.

Configuration settings in ASP.NET Core are hierarchical. In the new project you created, appsettings.json (see Figure 3) z. B. a top-level logging element with subordinate settings. These settings specify the minimum priority of the messages to be logged (via the "LogLevel" settings) and determine whether the logical area of ​​the app should be recorded at the time the message is logged (via "IncludeScopes"). To get nested settings like this, you can use the IConfiguration.GetSection method to get a single section of the configuration, or you can specify the full path to a specific setting (separated by colons). The value of "IncludeScopes" in the project can therefore be called up as follows:

Figure 3: ASP.NET Core file with the settings

When defining configuration settings using environment variables, the name of the environment variable should include all levels of the hierarchy. It can be separated by a colon (:) or a double underscore (__). An environment variable named "Logging__IncludeScopes" overwrites e.g. B. the setting "IncludeScopes" of the example file in Figure 3if the environment variable provider was added after the settings file, as is the case with CreateDefaultBuilder.

Because "WebHost.CreateDefaultBuilder" reads the configuration from "appsettings.json" and environment-specific JSON files, you will notice that the logging behavior changes when you change the environments ("appsettings.Development.json" overwrites the LogLevel standard settings of " appsettings.json ”with the more detailed levels“ debug ”and“ information ”). If you set the environment to Development before calling dotnet run, you should see some logging information in the console (which is good because it helps with debugging). Conversely, if you set the environment to Production, there will be no console logging except for warnings and errors (which is also good because console logging is slow and should be kept to a minimum in production).

If you have previous experience with ASP.NET Core 1.0 and 1.1, you may notice that the ConfigureAppConfiguration method is new in version 2.0. In previous versions it was common practice to create an "IConfiguration" as part of creating the startup type. Using the new ConfigureAppConfiguration method is a good alternative because it updates the IConfiguration object, which is stored in the application's dependency injection (DI) container for easy retrieval, and configuration settings even earlier in the life of your Application provides.

ASP.NET Core logging

As with the configuration setup, if you are familiar with previous versions of ASP.NET Core, remember that the logging setup was in Startup.cs, not Program.cs. In ASP.NET Core 2.0, logging can now be done when creating an IWebHost using the ConfigureLogging method.

It is still possible to set up logging in the startup (by using "services.Add-Logging" in "Startup.ConfigureServices"). However, configuring logging at web host creation time optimizes the startup type and makes logging available earlier in the app startup process.

Like the configuration, the ASP.NET Core logging is also expandable. Different providers are registered to log on different endpoints. Many vendors are immediately available with a reference to Microsoft.AspNetCore.All, and many more are available through the .NET developer community.

As you can see in the "WebHost.CreateDefaultBuilder" source code, logging providers can be added by calling vendor-specific extension methods such as "AddDebug" or "AddConsole" for "ILoggingBuilder". If you use "WebHost.CreateDefaultBuilder" but still want to add other logging providers than the standard "Debug" and "Console" providers, this can be done by an additional call to "ConfigureLogging" for the "IWebHostBuilder" which is returned by "CreateDefaultBuilder" .

After logging is configured and providers are registered, ASP.NET Core automatically logs messages related to tasks related to processing incoming requests. You can also log your own diagnostic messages by requesting an ILogger SQL Server object via dependency injection (see the next section for more on this). “ILogger.Log” calls and level-specific variants (such as “LogCritical”, “LogInformation” etc.) are used to log messages.

The startup type

After examining Program.cs to see how the web host is created, let's turn to Startup.cs. The start type your app should use is specified by calling "UseStartup" when creating the "IWebHost". Not much has changed in Startup.cs in ASP.NET Core 2.0 (except that the file has become simpler as logging and configuration are set up in Program.cs). Still, I would like to briefly introduce the two important methods of the startup type because they are so central to an ASP.NET Core app.

Dependency Injection: The ConfigureServices method of the startup type adds services to the application's dependency injection container. All ASP.NET Core apps have a standard dependency injection container to store services for later use. In this way, services can be provided without tight coupling to the components that depend on them. You have already seen some examples of this approach: ConfigureAppConfiguration and ConfigureLogging add services to the container for later use in your application. When an instance of a type is called at run time, ASP.NET Core automatically gets the object from the dependency injection container when it can.

For example, the starting class of your ASP.NET Core 2.0 project has B. Have a constructor that takes an IConfiguration parameter. This constructor is called automatically when your "IWebHost" starts executing. When this happens, ASP.NET Core provides the required IConfiguration argument from the dependency injection container.

Another example: if you want to log messages from a Razor page, you can use a logging object as a parameter to the page model's constructor (as in the startup's request for an IConfiguration object) or in the CSHTML file with the @inject- Request syntax. The following example shows this:

A similar approach could be used to get an IConfiguration object or any other type that has been registered as a service. This allows the startup type, razor pages, controller, etc. to depend loosely on services provided in the dependency injection container.

As mentioned at the beginning of this section, services are added to the dependency injection container in the Startup.ConfigureServices method. The project template you used to create your application already has a ConfigureServices: services.AddMvc call. As you can imagine, this is how services that the MVC framework needs are registered.

Another type of service that is often registered using the ConfigureServices method is Entity Framework Core. Entity Framework Core is not used in this example. Apps that use Entity Framework Core typically register the "DbContexts" required to work with Entity Framework models using calls to "services.AddDbContext".

You can also register your own types and services here by calling "services.AddTransient", "services.AddScoped", or "services.AddSingleton" (depending on the required lifetime for dependency injected objects). Registering as a singleton results in a single instance of the service that is returned on each request of its type, while registering as "transient" results in a new instance being created for each request. Adding it as “scoped” causes a single instance of a service to be used throughout the processing of a single HTTP request. For more details on dependency injection in ASP.NET Core, see bit.ly/2w7XtJI.

HTTP request processing pipeline and middleware: The second important method in the startup type is the Configure method. This is where the heart of the ASP.NET Core application is set up: its HTTP request processing pipeline. This method registers various middleware components that process incoming HTTP requests to generate responses.

In “Startup.Configure” middleware components are added to an “IApplicationBuilder” in order to form the processing pipeline. When a request is received, the first registered middleware component is called. This middleware component executes the required program logic and then calls the next middleware component in the pipeline or reverts to the previous middleware component (if one exists) when it has completely processed the response so that it can process the program logic that is required after an answer has been prepared. This pattern of calling the middleware component in the correct order when a request is received and then in the reverse order after processing is shown in Figure 4 shown.

Figure 4: ASP.NET Core middleware processing pipeline

As a concrete example shows Figure 5 the configure method from the template project. When a new request is received, depending on your environment, the DeveloperExceptionPage middleware or the ExceptionHandler middleware is used first (as before, this is configured with the environment variable "ASPNETCORE_ENVIRONMENT"). These middleware components do not initially perform many actions. But after the subsequent middleware has been executed and the request is on its way out of the middleware pipeline, they catch exceptions and handle them.

Figure 5: ASP.NET Core's Startup.Configure method sets up the middleware pipeline

In the next step, StaticFiles Middleware is called, which, if necessary, provides a static file (e.g. an image or a format template). If so, the pipeline stops and control is returned to the previous middleware (the exception handlers). If the StaticFiles middleware cannot provide a response, it calls the next middleware component: the MVC middleware. This middleware tries to route the request to an MVC controller (or Razor side) for final processing according to the routing options specified.

The order in which the middleware components are registered is extremely important. If UseStaticFiles was registered after UseMvc, the application would attempt to forward all HTTP requests to MVC controllers before checking for static files. This could lead to a noticeable deterioration in performance! If the middleware were registered for exception handling later in the pipeline, it would not be able to handle exceptions that occur in earlier middleware components.

Razor Pages

In addition to the ".csproj", "program.cs" and "startup.cs" files, your ASP.NET Core project also contains a "Pages" folder in which the application's Razor pages are stored. These pages are similar to MVC views, but requests can be routed directly to a Razor page without a separate controller. This allows page-based applications to be simplified and views and view models to be kept together. The model that the page supports can be included directly in the CSHTML page (in an @functions directive) or saved in a separate code file referenced with the @model directive.

For more information on Razor Pages, see the article "Simpler ASP.NET MVC Apps with Razor Pages," by Steve Smith, also featured in this issue.

Summary

Hopefully, this walkthrough succeeded in describing how to create a new ASP.NET Core 2.0 web application and introducing the contents of the new project templates. I have described the project contents from the optimized ".csproj" file, the entry point of the application and the web host configuration in "Program.cs", and the service and middleware registration in "Startup.cs".

If you want to dig deeper into the possibilities that ASP.NET Core offers, consider creating some new projects using the other templates like the Web API template or maybe some of the new SPA templates. You may also want to test deploying your ASP.NET Core app on Azure as an App Service web app, or package the application as a Linux or Windows Docker image. Of course, you can use the full documentation at docs.microsoft.com/aspnet/core for more information on the topics covered in this article and many more.


Mike Rousosworks as Principal Software Engineer in the .NET Customer Success Team. Rousos has been a member of the .NET team since 2004, working on technologies such as tracing, managed security, hosting and, more recently, .NET Core.

Thanks to the following Microsoft technical experts for reviewing this article: Glenn Condron and Ryan Nowak


Discuss this article on the MSDN Magazine forum