Asp.net Core Fundamentals_Part 1

The Startup class

The Startup class is where:

  • Services are registered in ConfigureServices and consumed across the app via dependency injection (DI)
  • Includes a Configure method to create the app’s request processing pipeline.

ConfigureServices and Configure are called by the ASP.NET Core runtime when the app starts:

The Startup class is specified when the app's host is built.

webBuilder.UseStartup<Startup>();

Only the following service types can be injected into the Startup constructor when using the Generic Host (IHostBuilder):

Multiple Startup

The class whose name suffix matches the current environment is prioritized. If the app is run in the Development environment and includes both a Startup class and a StartupDevelopment class, the StartupDevelopment class is used.

The ConfigureServices method is:

  • Called by the host before the Configure method to configure the app's services.

The Configure method

The Configure method is used to specify how the app responds to HTTP requests. The request pipeline is configured by adding middleware components to an IApplicationBuilder instance.Hosting creates an IApplicationBuilder and passes it directly to Configure.

Each middleware component in the request pipeline is responsible for invoking the next component in the pipeline or short-circuiting the chain, if appropriate.

Extend Startup with startup filters

Use IStartupFilter:

  • IStartupFilter is used by ASP.NET Core to add defaults to the beginning of the pipeline without having to make the app author explicitly register the default middleware.

Dependency injection (services)

done

Middleware

The request handling pipeline is composed as a series of middleware components. Each component performs operations on an HttpContext and either invokes the next middleware in the pipeline or terminates the request.

Host

On startup, an ASP.NET Core app builds a host. The host encapsulates all of the app’s resources, such as:

  • An HTTP server implementation
  • Middleware components
  • Logging
  • Dependency injection (DI) services
  • Configuration

There are two different hosts:

  • .NET Generic Host
  • ASP.NET Core Web Host

The .NET Generic Host is recommended. The ASP.NET Core Web Host is available only for backwards compatibility.

The following example creates a .NET Generic Host:

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>();
});
}

The CreateDefaultBuilder and ConfigureWebHostDefaults methods configure a host with a set of default options, such as:

  • Use Kestrel as the web server and enable IIS integration.
  • Load configuration from appsettings.json, appsettings.{Environment Name}.json, environment variables, command line arguments, and other configuration sources.

Servers

ASP.NET Core provides the following server implementations:

  • Kestrel is a cross-platform web server. Kestrel is often run in a reverse proxy configuration using IIS/Nginx/Apache(Linux OS). In ASP.NET Core 2.0 or later, Kestrel can be run as a public-facing edge server exposed directly to the Internet.
  • IIS HTTP Server is a server for Windows that uses IIS. With this server, the ASP.NET Core app and IIS run in the same process.
  • HTTP.sys is a server for Windows that isn’t used with IIS.

Dependency injection in ASP.NET Core

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies.

A dependency is an object that another object depends on.

Code dependencies should be avoided

  • To replace MyDependency with a different implementation, the IndexModel class must be modified.
  • This implementation is difficult to unit test. The app should use a mock or stub MyDependency class, which isn't possible with this approach.

Dependency injection addresses these problems through:

  • The use of an interface
  • The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it’s no longer needed.

Register groups of services with extension methods

The ASP.NET Core framework uses a convention for registering a group of related services. The convention is to use a single Add{GROUP_NAME} extension method to register all of the services required by a framework feature.

services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString(“DefaultConnection”)));

Related groups of registrations can be moved to an extension method to register services.

Service lifetimes

  • Transient objects are always different.
  • Scoped objects are the same for each request but different across each request.
  • Singleton objects are the same for every request.

Entity Framework contexts

By default, Entity Framework contexts are added to the service container using the scoped lifetime because web app database operations are normally scoped to the client request. To use a different lifetime, specify the lifetime by using an AddDbContext overload.

Design services for dependency injection

When designing services for dependency injection:

  • Avoid stateful, static classes and members. Avoid creating global state by designing apps to use singleton services instead.
  • Avoid direct instantiation of dependent classes within services.

If a class has a lot of injected dependencies, it might be a sign that the class has too many responsibilities and violates the Single Responsibility Principle (SRP). Attempt to refactor the class by moving some of its responsibilities into new classes.

Disposal of services

The container calls Dispose for the IDisposable types it creates. Services resolved from the container should never be disposed by the developer. If a type or factory is registered as a singleton, the container disposes the singleton automatically.

services.AddScoped<Service1>();

services.AddSingleton<Service2>();

Services not created by the service container

services.AddSingleton(new Service1());

  • The framework doesn’t dispose of the services automatically.
  • The developer is responsible for disposing the services.

Avoid using the service locator pattern. For example, don’t invoke GetService to obtain a service instance when you can use DI instead:

Recommended patterns for multi-tenancy in DI

Orchard Core is an application framework for building modular, multi-tenant applications on ASP.NET Core.

ASP.NET Core Middleware

Middleware is software that’s assembled into an app pipeline to handle requests and responses.

Request delegates handle each HTTP request. Request delegates are configured using Run, Map, and Use extension methods.

Create a middleware pipeline with IApplicationBuilder

Each delegate can perform operations before and after the next delegate. Exception-handling delegates should be called early in the pipeline, so they can catch exceptions that occur in later stages of the pipeline.

Chain multiple request delegates together with Use. The next parameter represents the next delegate in the pipeline.

Run delegates don’t receive a next parameter. The first Run delegate is always terminal and terminates the pipeline.

Middleware order

The Endpoint middleware in the preceding diagram executes the filter pipeline for the corresponding app type — MVC or Razor Pages.

The order is critical for security, performance, and functionality.

Exception/error handling

When the app runs in the Development environment:

  • Developer Exception Page Middleware (UseDeveloperExceptionPage) reports app runtime errors.
  • Database Error Page Middleware reports database runtime errors.

When the app runs in the Production environment:

  • Exception Handler Middleware (UseExceptionHandler) catches exceptions thrown in the following middlewares.
  • HTTP Strict Transport Security Protocol (HSTS) Middleware (UseHsts) adds the Strict-Transport-Security header.

Endpoint Routing Middleware (UseEndpoints with MapRazorPages) to add Razor Pages endpoints to the request pipeline.

Branch the middleware pipeline

Map branches the request pipeline based on matches of the given request path.

ASP.NET Core Web Host

ASP.NET Core apps configure and launch a host. The host is responsible for app 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, and IHostedService implementations.

In a web app, one of the IHostedService implementations is a web service that starts an HTTP server implementation.

Default builder settings

The CreateDefaultBuilder method:

  1. Loads host configuration from:
  • Environment variables prefixed with DOTNET_.

2. Loads app configuration from:

  • appsettings.json.
  • appsettings.{Environment}.json.

3. Adds the following logging providers:

  • Console
  • Debug

The ConfigureWebHostDefaults method:

  • Loads host configuration from environment variables prefixed with ASPNETCORE_.
  • Sets Kestrel server as the web server.
  • Adds Host Filtering middleware.
  • Enables IIS integration.

Framework-provided services

The following services are registered automatically:

IHostApplicationLifetime

Inject the IHostApplicationLifetime (formerly IApplicationLifetime) service into any class to handle post-startup and graceful shutdown tasks.

IHostLifetime

The IHostLifetime implementation controls when the host starts and when it stops.

IHostEnvironment

Inject the IHostEnvironment service into a class to get information about the following settings:

Web server implementations in ASP.NET Core

Kestrel

Kestrel is the default web server specified by the ASP.NET Core project templates.

Use Kestrel:

  • By itself as an edge server processing requests directly from a network, including the Internet.

Either hosting configuration — with or without a reverse proxy server — is supported.

ASP.NET Core ships with the following:

  • Kestrel server is the default, cross-platform HTTP server implementation.
  • IIS HTTP Server is an in-process server for IIS.
  • HTTP.sys server - If ASP.NET Core apps are run on Windows, HTTP.sys is an alternative to Kestrel. Kestrel is generally recommended for best performance.

When using IIS or IIS Express, the app either runs:

The ASP.NET Core Module is a native IIS module that handles native IIS requests between IIS and the in-process IIS HTTP Server or Kestrel.

Hosting models

Using in-process hosting, an ASP.NET Core app runs in the same process as its IIS worker process. In-process hosting provides improved performance over out-of-process hosting because requests aren’t proxied over the loopback adapter

Open Web Interface for .NET (OWIN) with ASP.NET Core

ASP.NET Core supports the Open Web Interface for .NET (OWIN). OWIN allows web apps to be decoupled from web servers. It defines a standard way for middleware to be used in a pipeline to handle requests and associated responses.

Server startup

The server is launched when the Integrated Development Environment (IDE) or editor starts the app:

Configuration in ASP.NET Core

CreateDefaultBuilder provides default configuration for the app in the following order:

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.
  • User secrets when the app runs in the Development environment.
  • Environment variables.
  • Command-line arguments.

Bind hierarchical configuration data using the options pattern

The preferred way to read related configuration values is using the options pattern.

“Position”: {
“Title”: “Editor”,
“Name”: “Joe Smith”
}

public class PositionOptions
{
public const string Position = “Position”;

public string Title { get; set; }
public string Name { get; set; }
}

public void ConfigureServices(IServiceCollection services)
{
services.Configure<PositionOptions>(Configuration.GetSection(
PositionOptions.Position));
services.AddRazorPages();
}

public class Test2Model : PageModel
{
private readonly PositionOptions _options;

public Test2Model(IOptions<PositionOptions> options)
{
_options = options.Value;
}

public ContentResult OnGet()
{
return Content($”Title: {_options.Title} \n” +
$”Name: {_options.Name}”);
}
}

In the preceding code, changes to the JSON configuration file after the app has started are not read. To read changes after the app has started, use IOptionsSnapshot.

Using the default configuration, the appsettings.json and appsettings.Environment.json files are enabled with reloadOnChange: true.

Related groups of registrations can be moved to an extension method to register services.

Security and user secrets

Configuration data guidelines:

  • Never store passwords or other sensitive data in configuration provider code or in plain text configuration files. The Secret Manager tool can be used to store secrets in development.
  • Don’t use production secrets in development or test environments.
  • Specify secrets outside of the project so that they can’t be accidentally committed to a source code repository.

By default, the user secrets configuration source is registered after the JSON configuration sources.

Environment variables

Using the default configuration, the EnvironmentVariablesConfigurationProvider loads configuration from environment variable key-value pairs after reading appsettings.json, appsettings.Environment.json, and user secrets.

The : separator doesn't work with environment variable hierarchical keys on all platforms. __, the double underscore, is:

Call AddEnvironmentVariables with a string to specify a prefix for environment variables:

  • Environment variables set with the MyCustomPrefix_ prefix override the default configuration providers. This includes environment variables without the prefix.
  • The prefix is stripped off when the configuration key-value pairs are read.

Naming of environment variables

Environment variable names reflect the structure of an appsettings.json file.

Environment variables set in launchSettings.json

Environment variables set in launchSettings.json override those set in the system environment.

GetSection and GetChildren methods are available to isolate sections and children of a section in the configuration data.

launch.json/launchSettings.json are tooling configuration files for the Development environment,

Options pattern in ASP.NET Core

The options pattern uses classes to provide strongly typed access to groups of related settings. When configuration settings are isolated by scenario into separate classes, the app adheres to two important software engineering principles:

Options interfaces

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

  • Is useful in scenarios where options should be recomputed on every request.

Named options support using IConfigureNamedOptions

Named options:

  • Are useful when multiple configuration sections bind to the same properties.
  • Are case sensitive.

Options validation

Options validation enables option values to be validated.

IValidateOptions for complex validation

Environments

To determine the runtime environment, ASP.NET Core reads from the following environment variables:

  1. DOTNET_ENVIRONMENT
  2. ASPNETCORE_ENVIRONMENT when ConfigureWebHostDefaults is called. The ASPNETCORE_ENVIRONMENT value overrides DOTNET_ENVIRONMENT.

The following code:

The launchSettings.json file:

  • Is only used on the local development machine.
  • Is not deployed.
  • contains profile settings.

The value of commandName can specify the web server to launch.

  • IISExpress : Launches IIS Express.
  • IIS : No web server launched. IIS is expected to be available.
  • Project : Launches Kestrel.

Logging in .NET Core and ASP.NET Core

  • .NET Core supports a logging API that works with a variety of built-in and third-party logging providers..NET Core supports a logging API that works with a variety of built-in and third-party logging providers.

Call CreateDefaultBuilder, which adds the following logging providers:

To override the default set of logging providers added by Host.CreateDefaultBuilder, call ClearProviders and add the required logging providers.

Create logs

To create logs, use an ILogger<TCategoryName> object from dependency injection (DI).

Log category

When an ILogger object is created, a category is specified. That category is included with each log message created by that instance of ILogger.

Log event ID

Each log can specify an event ID. The sample app uses the MyLogEvents class to define event IDs:

An event ID associates a set of events. For example, all logs related to displaying a list of items on a page might be 1001.

Log message template

Each log API uses a message template. The message template can contain placeholders for which arguments are provided.

Default log level

If the default log level is not set, the default log level value is Information.

Log scopes

A scope can group a set of logical operations. This grouping can be used to attach the same data to each log that’s created as part of a set. For example, every log created as part of processing a transaction can include the transaction ID.

A scope:

“IncludeScopes”: true, // Required to use Scopes.

Built-in logging providers

ASP.NET Core includes the following logging providers as part of the shared framework:

Event Source

The EventSource provider writes to a cross-platform event source with the name Microsoft-Extensions-Logging. On Windows, the provider uses ETW.

dotnet trace tooling

Use the dotnet trace tooling to collect a trace from an app:

Trace for performance analysis utility

Perfview

Use the PerfView utility to collect and view logs. There are other tools for viewing ETW logs, but PerfView provides the best experience for working with the ETW events emitted by ASP.NET Core.

PerfView is a free performance-analysis tool that helps isolate CPU and memory-related performance issues. It is a Windows tool, but it also has some support for analyzing data collected on Linux machines.

Azure Application Insights

The Microsoft.Extensions.Logging.ApplicationInsights provider package writes logs to Azure Application Insights. Application Insights is a service that monitors a web app and provides tools for querying and analyzing the telemetry data. If you use this provider, you can query and analyze your logs by using the Application Insights tools.

The logging provider is included as a dependency of Microsoft.ApplicationInsights.AspNetCore, which is the package that provides all available telemetry for ASP.NET Core. If you use this package, you don’t have to install the provider package.

Using a third-party framework (NLog) is similar to using one of the built-in providers:

  1. Add a NuGet package to your project.
  2. Call an ILoggerFactory extension method provided by the logging framework.

No asynchronous logger methods

Logging should be so fast that it isn’t worth the performance cost of asynchronous code. If a logging data store is slow, don’t write to it directly. Consider writing the log messages to a fast store initially, then moving them to the slow store later. For example, when logging to SQL Server, don’t do so directly in a Log method, since the Log methods are synchronous. Instead, synchronously add log messages to an in-memory queue and have a background worker pull the messages out of the queue to do the asynchronous work of pushing data to SQL Server.

Apply log filter rules in code

The preferred approach for setting log filter rules is by using Configuration.

Routing in ASP.NET Core

Routing is registered in the middleware pipeline in Startup.Configure.

Routing uses a pair of middleware, registered by UseRouting and UseEndpoints (The MapGet method is used to define an endpoint)

The following example shows routing with health checks and authorization:

  1. Middleware can run before UseRouting to modify the data that routing operates upon eg: UseRewriter, UseHttpMethodOverride, or UsePathBase.

2. Middleware can run between UseRouting and UseEndpoints

  • Usually inspects metadata to understand the endpoints and often makes security decisions, as done by UseAuthorization and UseCors.
  • The combination of middleware and metadata allows configuring policies per-endpoint.