ASP.NET Core 6框架揭秘实例演示[01]: 编程初体验

本篇提供的20个简单的演示实例基本涵盖了ASP.NET Core 6基本的编程模式,我们不仅会利用它们来演示针对控制台、API、MVC、gRPC应用的构建与编程,还会演示Dapr在.NET 6中的应用。除此之外,这20个实例还涵盖了针对依赖注入、配置选项、日志记录的应用。[本篇节选自《ASP.NET Core 6框架揭秘》第一章]

[101]利用命令行创建.NET程序(源代码)
[102]采用Minimal API构建ASP.NET Core程序(源代码)
[103]一步创建WebApplication对象(源代码)
[104]使用原始形态的中间件(源代码)
[105]使用中间件委托变体(1)(源代码)
[106]使用中间件委托变体(2)(源代码)
[107]定义强类型中间件类型(源代码)
[108]定义基于约定的中间件类型(构造函数注入)(源代码)
[109]定义基于约定的中间件类型(方法注入)(源代码)
[110]配置的应用(源代码)
[111]Options的应用(源代码)
[112]日志的应用(源代码)

[101]利用命令行创建.NET程序

我们按照图1所示的方式执行“dotnet new”命令(dotnet new console -n App)创建一个名为“App”的控制台程序。该命令执行之后会在当前工作目录创建一个由指定应用名称命名的子目录,并将生成的文件存放在里面。

5c8320c9de6d195a56c2725def9a9f62.png
图1 执行“dotnet new”命令创建一个控制台程序

.csproj文件最终是为MSBuild服务的,该文件提供了相关的配置来控制MSBuild针对当前项目的编译和发布行为。如下所示的就是App.csproj文件的全部内容,如果你曾经查看过传统.NET Framework下的.csproj文件,你会惊叹于这个App.csproj文件内容的简洁。.NET 6下的项目文件的简洁源于对SDK的应用。不同的应用类型会采用不同的SDK,比如我们创建的这个控制台应用采用的SDK为“Microsoft.NET.Sdk”,ASP.NET应用会采用另一个名为“Microsoft.NET.Sdk.Web”的SDK。SDK相等于为某种类型的项目制定了一份面向MSBuild的基准配置,如果在项目文件的<Project>根节点设置了具体的SDK,意味着直接将这份基准配置继承下来。

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net6.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable>
</PropertyGroup></Project>t>

如上面的代码片段所示,与项目相关的属性可以分组定义在项目文件的<PropertyGroup>节点下。这个App.csproj文件定义了四个属性,其中OutputType和TargetFramework属性表示编译输出类型与采用的目标框架。由于我们创建的是一个针对 .NET 6的可执行控制台应用,所以TargetFramework和OutputType分别设置为“net6.0”和“Exe”。项目的ImplicitUsings属性与C# 10提供的一个叫做“全局命名空间”新特性有关,另一个名为Nullable的属性与C#与一个名为“空值(Null)验证”的特性有关。

如下所示的就是项目目录下的生成的Program.cs文件的内容。可以看出整个文件只有两行文字,其中一行还是注释。这唯一的一行代码调用了Console类型的静态方法将字符串“Hello, World!”输出到控制台上。这里体现了C# 10另一个被称为“顶级语句(Top-level Statements)”的新特性——入口程序的代码可以作为顶层语句独立存在。

// See https://aka.ms/new-console-template for more informationConsole.WriteLine("Hello, World!"););

针对 .NET应用的编译和运行同样可以执行“dotnet.exe”命令行完成的。如图2所示,在将项目根目录作为工作目录后,我们执行“dotnet build”命令对这个控制台应用实施编译。由于默认采用Debug编译模式,所以编译生成的程序集会保存在“\bin\Debug\”目录下。同一个应用可以采用多个目标框架,针对不同目标框架编译生成的程序集是会放在不同的目录下。由于我们创建的是针对 .NET 6.0的应用程序,所以最终生成的程序集被保存在“\bin\Debug\net6.0\”目录下。

  0d11dd833c8d3d1bb94afac48b157568.png
图2 执行“dotnet build”命令编译一个控制台程序

如果查看编译的输出目录,可以发现两个同名(App)的程序集文件,一个是App.dll,另一个是App.exe,后者在尺寸上会大很多。App.exe是一个可以直接运行的可执行文件,而App.dll仅仅是一个单纯的动态链接库,需要借助命令行dotnet才能执行。如图3所示,当我们执行“dotnet run”命令后,编译后的程序随即被执行,“Hello, World!”字符串被直接打印在控制台上。执行“dotnet run”命令启动程序之前其实无须显式执行“dotnet build”命令对源代码实施编译,因为该命令会自动触发编译操作。在执行“dotnet”命令启动应用程序集时,我们也可以直接指定启动程序集的路径(“dotnet bin\Debug\net6.0\App.dll”)。实际上dotnet run主要用在开发测试中,dotnet {AppName}.dll的方式才是部署环境(比如Docker容器)中采用的启动方式。

586ac2428b53a55eb1e5769365c14b41.png
图3 执行dotnet命令运行一个控制台程序

[102]采用Minimal API构建ASP.NET Core程序

前面利用dotnet new命令创建了一个简单的控制台程序,接下来我们将其改造成一个ASP.NET Core应用。我们在前面已经说过,不同的应用类型会采用不同的SDK,所以我们直接修改App.csproj文件将SDK设置为“Microsoft.NET.Sdk.Web”。由于不需要利用生成的.exe文件来启动ASP.NET Core应用,所以应该将XML元素<OutputType>Exe</OutputType>从<PropertyGroup>节点中删除。

<Project Sdk="Microsoft.NET.Sdk.Web"><PropertyGroup><TargetFramework>net6.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable>
</PropertyGroup></Project>

ASP.NET Core (Core)应用的承载(Hosting)经历了三次较大的变迁,由于最新的承载方式提供的API最为简洁且依赖最小,我们将它称为 “Minimal API” 。本书除了在第16章 “应用承载(上)” 会涉及到其他两种承载模式外,本书提供的所有演示实例均会使用Minimal API。如下所示的是我们采用这种编程模式编写的第一个Hello World程序。

RequestDelegate handler = context => context.Response.WriteAsync("Hello, World!");
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.Run(handler: handler);
app.Run();

上面的代码片段涉及到三个重要的对象,其中WebApplication对象表示承载的应用,Minimal API采用“构建者(Builder)”模式来构建它,此构建者体现为一个WebApplicationBuilder对象。如代码片段所示,我们调用WebApplication类型的静态工厂方法CreateBuilder创建了一个WebApplicationBuilder对象,该方法的参数args代表命令行参数数组。在调用此该对象的Build方法将WebApplication对象构建出来后,我们调用了它的Run扩展方法并使用一个RequestDelegate对象作为其参数。RequestDelegate虽然是一个简单的委托类型,但是它在ASP.NET Core框架体系中地位非凡,我们现在先来对它做一个简单的介绍。

当一个ASP.NET Core启动之后,它会使用注册的服务器绑定到指定的端口进行请求监听。当接收抵达的请求之后,一个通过HttpContext对象表示的上下文对象会被创建出来。我们不仅可以从这个上下文中提取出所有与当前请求相关的信息,还能直接使用该上下文完成对请求的响应。关于这一点完全可以从HttpContext这个抽象类如下两个核心属性Request和Response看出来。

public abstract class HttpContext{     public abstract HttpRequest Request { get }     public abstract HttpResponse Response { get }...}

由于ASP.NET Core应用针对请求的处理总是在一个HttpContext上下文中进行,所以针对请求的处理器可以表示为一个Func<HttpContext, Task>类型的委托。由于这样的委托会被广泛地使用,所以ASP.NET Core直接定义了一个专门的委托类型,就是我们在程序中使用到的RequestDelegate。从如下所示的针对RequestDelegate类型的定义可以看出,它本质上就是一个Func<HttpContext, Task>委托。

public delegate Task RequestDelegate(HttpContext context);

再次回到演示程序。我们首先创建了一个RequestDelegate委托,对应的目标方法会在响应输出流中写入字符串 “Hello, World!” 。我们将此委托作为参数调用WebApplication对象的Run扩展方法,这个调用可以理解为将这个委托作为所有请求的处理器,接收到的所有请求都将通过这个委托来处理。演示程序最后调用WebApplication另一个无参Run扩展方法是为了启动承载的应用。在Visual Studio下,我们可以直接按F5(或者Ctrl + F5)启动该程序,当然针对命令行 “dotnet run” 命令的应用启动方式依然有效,本书提供的演示实例大都会采用这种方式。

如图4所示,我们以命令行方式启动程序后,控制台上会出现ASP.NET Core框架输出的日志,通过日志表明应用已经开始在默认的两个终结点(http://localhost:5000和https://localhost:5001)监听请求了。我们使用浏览器针对这两个终结点发送了两个请求,均得到一致的响应。从响应的内容可以看出应用正是利用我们指定的RequestDelegate委托处理请求的。

0a3937b0b776e7780defe3958f2922be.jpeg

图4 启动应用程序并利用浏览器进行访问

[103]一步创建WebApplication对象

上面演示的程序先调用定义在WebApplication类型的静态工厂方法CreateBuilder创建一个WebApplicationBuilder对象,再利用后者构建一个代表承载应用的WebApplication对象。WebApplicationBuilder提供了很多用来对构建WebApplication进行设置的API,但是我们的演示实例并未使用到它们,此时我们可以直接调用静态工厂方法Create将WebApplication对象创建出来。在如下所示的改写程序中,我们直接将请求处理器定义成一个本地静态方法HandleAsync。

var app = WebApplication.Create(args);
app.Run(handler: HandleAsync);
app.Run();
static Task HandleAsync(HttpContext httpContext)   
=> httpContext.Response.WriteAsync("Hello, World!");

[104]使用原始形态的中间件

承载的ASP.NET Core应用最终体现为由注册中间件构建的请求处理管道。在服务器接收到请求并将成功构建出HttpContext上下文之后,会将请求交付给这个管道进行处理。待管道完成了处理任务之后,控制权再次回到服务器的手中,它会将处理的结果转换成响应发送出去。从应用编程的角度来看,这个管道体现为上述的RequestDelegate委托,组成它的单个中间件则体现为另一个类型为Func<RequestDelegate,RequestDelegate>的委托,该委托的输入和输出都是一个RequestDelegate对象,前者表示由后续中间件构建的管道,后者代表将当前中间件纳入此管道后生成的新管道。

在上面演示的实例中,我们将一个RequestDelegate委托作为参数调用了WebApplication的Run扩展方法,我们当时说这是为应用设置一个请求处理器。其实这种说法不够准确,该方法仅仅是注册一个中间件而已。说得更加具体一点,这个方法用于注册处于管道末端的中间件。为了让读者体验到中间件和管道针对请求的处理,我们对上面演示应用进行了如下的改写。

var app = WebApplication.Create(args);
IApplicationBuilder appBuilder = app;
appBuilder.Use(middleware: HelloMiddleware).Use(middleware: WorldMiddleware);
app.Run();
static RequestDelegate HelloMiddleware(RequestDelegate next)=> async httpContext => {await httpContext.Response.WriteAsync("Hello, ");await next(httpContext);
};static RequestDelegate WorldMiddleware(RequestDelegate next)
=> httpContext => httpContext.Response.WriteAsync("World!");

由于中间件体现为一个Func<RequestDelegate,RequestDelegate>委托,所以我们利用上面定义的两个与该委托类型具有一致声明的本地静态方法HelloMiddleware和WorldMiddleware来表示对应的中间件。我们将完整的文本“Hello, World!”拆分为“Hello, ”和“World!”两段,分别由上述两个终结点写入响应输出流。在创建出代表承载应用的WebApplication对象之后,我们将它转换成IApplicationBuilder接口类型,并调用其Use方法完成了对上述两个中间件的注册(由于WebApplication类型显式实现了定义在IApplicationBuilder接口中的Use方法,我们不得不进行类型转换)。如果利用浏览器采用相同的地址请求启动后的应用,我们依然可以得到如图4所示的响应内容。

[105]使用中间件委托变体(1)

虽然中间件最终总是体现为一个Func<RequestDelegate,RequestDelegate>委托,但是我们在开发过程中可以采用各种不同的形式来定义中间件,比如我们可以将中间件定义成如下两种类型的委托。这两个委托内容分别使用作为输入参数的RequestDelegate和Func<Task>完整对后续管道的调用。

  • Func<HttpContext, RequestDelegate, Task>

  • Func<HttpContext, Func<Task>, Task>

我们现在来演示如何使用Func<HttpContext, RequestDelegate, Task>委托的形式来定义中间件。如下面的代码片段所示,我们将HelloMiddleware和WorldMiddleware替换成了与Func<HttpContext, RequestDelegate, Task>委托类型具有一致声明的本地静态方法。

var app = WebApplication.Create(args);
app.Use(middleware: HelloMiddleware).Use(middleware: WorldMiddleware);
app.Run();
static async Task HelloMiddleware(HttpContext httpContext, RequestDelegate next)
{await httpContext.Response.WriteAsync("Hello, ");await next(httpContext);
};
static Task WorldMiddleware(HttpContext httpContext, RequestDelegate next) => httpContext.Response.WriteAsync("World!");

[106]使用中间件委托变体(2)

下面的程序以类似的方式将这两个中间件替换成与Func<HttpContext, Func<Task>, Task>委托类型具有一致声明的本地方法。当我们调用WebApplication的Use方法将这两种“变体”注册为中间件的时候,该方法内部会将提供的委托转换成Func<RequestDelegate,RequestDelegate>类型。

var app = WebApplication.Create(args);
app.Use(middleware: HelloMiddleware).Use(middleware: WorldMiddleware);
app.Run();
static async Task HelloMiddleware(HttpContext httpContext, Func<Task> next)
{await httpContext.Response.WriteAsync("Hello, ");await next();
};
static Task WorldMiddleware(HttpContext httpContext, Func<Task> next)    => httpContext.Response.WriteAsync("World!");

[107]定义强类型中间件类型

当我们试图利用一个自定义中间件来完成某种请求处理功能时,其实很少会将中间件定义成上述的这三种委托形式,基本上都会将其定义成一个具体的类型。中间件类型有定义方式,一种是直接实现IMiddleware接口,本书将其称为“强类型”的中间件定义方式。我们现在就采用这样的方式定义一个简单的中间件类型。不论在定义中间件类型,还是定义其他的服务类型,如果它们具有对其他服务的依赖,我们都会采用依赖注入(Dependency Injection)的方式将它们整合在一起。整个ASP.NET Core框架就建立在依赖注入框架之上,依赖注入已经成为ASP.NET Core最基本的编程方式 。我们接下来会演示依赖注入在自定义中间件类型中的应用。

在前面演示的实例中,我们利用中间件写入以“硬编码”方式指定的问候语“Hello, World!”,现在我们选择由如下这个IGreeter接口表示的服务根据指定的时间来提供对应的问候语,Greeter类型是该接口的默认实现。这里需要提前说明一下,本书提供的所有的演示实例都以“App”命名,独立定义的类型默认会定义在约定的“App”命名空间下。为了节省篇幅,接下来提供的类型定义代码片段将不再提供所在的命名空间,当启动应用程序出现针对“App”命名空间的导入时不要感到奇怪。

namespace App
{    public interface IGreeter{        string Greet(DateTimeOffset time);}    public class Greeter : IGreeter{        public string Greet(DateTimeOffset time) => time.Hour switch{var h when h >= 5 && h < 12 => "Good morning!",var h when h >= 12 && h < 17 => "Good afternoon!",_ => "Good evening!"};}
}

我们定义了如下这个名为GreetingMiddleware的中间件类型。如代码片段所示,该类型实现了IMiddleware接口,针对请求的处理实现在InvokeAsync方法中。我们在GreetingMiddleware类型的构造函数中注入了IGreeter对象,并利用它在实现的InvokeAsync方法中根据当前时间来提供对应的问候语,后者将作为请求的响应内容。

public class GreetingMiddleware : IMiddleware
{    private readonly IGreeter _greeter;    public GreetingMiddleware(IGreeter greeter)    => _greeter = greeter;    public Task InvokeAsync(HttpContext context, RequestDelegate next)=> context.Response.WriteAsync(_greeter.Greet(DateTimeOffset.Now));
}

针对GreetingMiddleware中间件的应用体现在如下的程序中。如代码片段所示,我们调用了WebApplication对象的UseMiddleware<GreetingMiddleware>扩展方法注册了这个中间件。由于强类型中间件实例是由依赖注入容器在需要的时候实时提供的,所以我们必须预先将它注册为服务。注册的注册最终会添加到WebApplicationBuilder的Services属性返回的IServiceCollection对象上,我们在得到这个对象后通过调用它的AddSingleton< GreetingMiddleware >方法将该中间件注册为“单例服务”。由于中间件依赖IGreeter服务,所以我们调用AddSingleton<IGreeter, Greeter>扩展方法对该服务进行了注册。

using App;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IGreeter, Greeter>().AddSingleton<GreetingMiddleware>();
var app = builder.Build();
app.UseMiddleware<GreetingMiddleware>();
app.Run();

该程序启动之后,针对它的请求会得到根据当前时间的生成问候语。如图5所示,由于目前的时间为晚上七点,所以浏览器上显示“Good evening!”。

77ea73565f123711241e3ae649801855.jpeg
图5 自定义中间件返回的问候语

[108]定义基于约定的中间件类型(构造函数注入)

中间件类型其实并不一定非得实现某个接口,或者继承某个基类,按照既定的约定进行定义即可。按照ASP.NET Core的约定,中间件类型需要定义成一个公共实例类型(静态类型无效),其构造函数可以注入任意的依赖服务,但必须包含一个RequestDelegate类型的参数,该参数表示由后续中间件构建的管道,当前中间件利用它将请求分发给后续管道作进一步处理。针对请求的处理实现在一个命名为InvokeAsync或者Invoke的方法中,该方法返回类型为Task, 第一个参数并绑定为当前的HttpContext上下文,所以GreetingMiddleware中间件类型可以改写成如下的形式。

public class GreetingMiddleware
{    private readonly IGreeter _greeter;    public GreetingMiddleware(RequestDelegate next, IGreeter greeter) => _greeter = greeter;    public Task InvokeAsync(HttpContext context) => context.Response.WriteAsync(_greeter.Greet(DateTimeOffset.Now));
}

强类型的中间件实例是在对请求进行处理的时候由依赖注入容器实时提供的,按照约定定义的中间件实例则不同,当我们在注册中间件的时候就已经利用依赖注入容器将它创建出来,所以前者可以采用不同的生命周期模式,后者总是一个单例对象。也正是因为这个原因,我们不需要将中间件注册为服务。

using App;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IGreeter, Greeter>();
var app = builder.Build();
app.UseMiddleware<GreetingMiddleware>();
app.Run();

[109]定义基于约定的中间件类型(方法注入)

对于按照约定定义的中间件类型,依赖服务不一定非要注入到构造函数中,它们选择直接注入到InvokeAsync或者Invoke方法中,所以上面这个GreetingMiddleware中间件也可以定义成如下的形式。对于按照约定定义的中间件类型,构造函数注入和方法注入并不是等效,两者之间的差异会在第18章“应用承载(下)”中进行介绍。

public class GreetingMiddleware
{    public GreetingMiddleware(RequestDelegate next){}    public Task InvokeAsync(HttpContext context, IGreeter greeter) => context.Response.WriteAsync(greeter.Greet(DateTimeOffset.Now));
}

[110]配置的应用

开发ASP.NET Core应用过程会广泛使用到配置(Configuration),ASP.NET Core采用了一个非常灵活的配置框架,我们可以存储在任何载体的数据作为配置源。我们还可以将结构化的配置转换成对应的选项(Options)类型,以强类型的方式来使用它们。针对配置选项的系统介绍被放在第5章“配置选项(上)”和第6章“配置选项(下)”中,我们先在这里“预热”一下。在前面演示的实例中,Greeter类型针对指定时间提供的问候语依然是以“硬编码”的方式提供的,现在我们选择将它们放到配置文件以方便进行调整中。为此我们在项目根目录下添加一个名为“appsettings.json”的配置文件,并将三条问候语以如下的形式定义在这个JSON文件中。

{"greeting": {"morning": "Good morning!","afternoon": "Good afternoon!","evening": "Good evening!"}
}

ASP.NET Core应用中的配置通过IConfiguration对象表示,我们可以采用依赖注入的形式“自由”地使用它。对于演示的程序来说,我们只需要按照如下的方式将IConfiguration对象注入到Greeter类型的构造函数中,然后调用其GetSection方法得到定义了上述问候语的配置节(“greeting”)。在实现的Greet方法中,我们以索引的方式利用指定的Key(“morning”、“afternoon”和“evening”)提取对应的问候语。由于应用启动的时候会自动加载这个按照约定命名的(“appsettings.json”)配置文件,所以演示程序的其他地方不要作任何修改。

public class Greeter : IGreeter
{    private readonly IConfiguration _configuration;    public Greeter(IConfiguration configuration)    => _configuration = configuration.GetSection("greeting");    public string Greet(DateTimeOffset time) => time.Hour switch {var h when h >= 5 && h < 12 => _configuration["morning"],var h when h >= 12 && h < 17=> _configuration["afternoon"],_ => _configuration["evening"],};
}

[111]Options的应用

正如前面所说,将结构化的配置转换成对应类型的Options对象,以强类型的方式来使用它们是更加推荐的编程模式。为此我们为配置的三条问候语定义了如下这个GreetingOptions配置选项类型。

public class GreetingOptions
{    public string Morning { get; set; }   public string Afternoon { get; set; }    public string Evening { get; set; } 
}

虽然Options对象不能直接以依赖服务的形式进行注入,但却可以由注入的IOptions<TOptions>对象来提供。如下面的代码片段所示,我们在Greeter类型的构造函数中注入了IOptions<GreetingOptions>对象,并利用其Value属性中得到了我们需要的GreetingOptions对象。在有了这个对象后,实现的Greet方法中只需要从对应的属性中获取相应的问候语就可以了。

public class Greeter : IGreeter
{    private readonly GreetingOptions _options;    public Greeter(IOptions<GreetingOptions> optionsAccessor)     => _options = optionsAccessor.Value;    public string Greet(DateTimeOffset time) => time.Hour switch{var h when h >= 5 && h < 12 => _options.Morning,var h when h >= 12 && h < 17 => _options.Afternoon,_ => _options.Evening};
}

由于IOptions<GreetingOptions>对象提供的配置选项不能无中生有(实际上存在于配置中),我们需要将对应的配置节(“greeting”)绑定到GreetingOptions对象上。这项工作其实也属于服务注册的范畴,具体可以按照如下的形式调用IServiceCollection对象的Configure<TOptions>扩展方法来完成。如代码片段所示,代表应用整体配置的IConfiguration对象来源于WebApplicationBuilder的Configuration属性。

using App;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IGreeter, Greeter>().Configure<GreetingOptions>(builder.Configuration.GetSection("greeting"));
var app = builder.Build();
app.UseMiddleware<GreetingMiddleware>();
app.Run();

[112]日志的应用

诊断日志对于纠错排错必不可少。ASP.NET Core采用的诊断日志框架强大、易用且灵活。在我们演示的程序中,Greeter类型会根据指定的时间返回对应的问候语,现在我们将时间和对应的问候语以日志的方式记录下来看看两者是否匹配。我们在前面曾说过,依赖注入是ASP.NET Core应用最基本的编程模式。我们将涉及的功能(不论是业务相关的还是业务无关的)进行拆分,最终以具有不同粒度的服务将整个应用化整为零,服务之间的依赖关系直接以注入的方式来解决。我们在前面演示了针对配置选项的注入,接下来我们用来记录日志的ILogger对象依然采用注入的方式获得。如下面的代码片段所示,我们在Greeter类型的构造函数中注入了ILogger<Greeter>对象。在实现的Greet方法中,我们调用该对象的LogInformation扩展方法记录了一条Information等级的日志,日志内容体现了时间与问候语文本之间的映射关系。

public class Greeter : IGreeter
{    private readonly GreetingOptions _options;    private readonly ILogger _logger;    public Greeter(IOptions<GreetingOptions> optionsAccessor, ILogger<Greeter> logger){_options = optionsAccessor.Value;_logger = logger;}    public string Greet(DateTimeOffset time){var message = time.Hour switch{var h when h >= 5 && h < 12 => _options.Morning,var h when h >= 12 && h < 17 => _options.Afternoon,_ => _options.Evening};_logger.LogInformation("{time} => {message}",time, message);        return message;}
}

采用Minimal API编写的ASP.NET Core应用会默认将诊断日志整合进来,所以整个演示程序的其它地方都不要修改。当修改后的应用启动之后,针对每一个请求都会通过日志留下“痕迹”。由于控制台是默认开启的日志输出渠道之一,日志内容直接会输出到控制台上。图5所示的是以命令行形式启动应用的控制台,上面显示的都是以日志形式输出的内容。在众多系统日志中,我们发现有一条是由Greeter对象输出的。

3b79e2a8a1d3c31468e82f7892926d88.png
图5 输出到控制台上的日志

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/284659.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DBeaverEE 21.1.0安装指南

1、 安装jdk11 2、 配置环境变量 将jdk11安装目录加入path&#xff1a;C:\Program Files\Java\jdk-11.0.10\bin3、 安装DBEE 21.1 4、 将dbeaver-agent文件夹复制到DBEE安装目录 5、将DBEE安装目录下的jre目录删除或改名 6、 修改dbeaver.ini文件&#xff0c;在文件最后添加…

跟风学Docker之四:Docker网络解决方案

2019独角兽企业重金招聘Python工程师标准>>> 跟风学Docker之四&#xff1a;Docker网络解决方案 博客分类&#xff1a; docker 前言&#xff1a;前面的部分一直都是单机跑docker&#xff0c;但实际生产环境不可能只用一台来跑。肯定会用到多台&#xff0c;因为他们都…

【测绘程序设计】坐标方位角推算神器(C#版)

本文讲解利用C#语言实现坐标方位角推算,附源码赠送。 1. 神器效果展示 (1)连接角为左角 (2)连接角为右角 2. 方位角推算原理速递 (1)原理示意图

原型模式——创建型模式

2019独角兽企业重金招聘Python工程师标准>>> 思路&#xff1a; 马上又到找工作的时候了&#xff0c;当我们在准备一份份简历的时候有没有考虑过这样一个问题&#xff1f; 面对不同的工作岗位我们需要准备不同的求职简历&#xff0c;但是这样的几份不同的简历中还是有…

如何获取 ASP.NET Core 当前启动地址?

前言上次&#xff0c;我们介绍了配置ASP.NET Core启动地址的多种方法。那么&#xff0c;如何通过代码方式&#xff0c;获取启动后的地址&#xff1f;WebApplication.Urls 对象使用 WebApplication.Urls.Add 方法可以添加启动地址。那么&#xff0c;使用 WebApplication.Urls 应…

【CASS精品教程】CASS9.1查询功能大全(坐标、长度、面积、方位角)

文章目录 1. 查询指定点坐标2. 查询两点距离及方位3. 查询线长4. 查询实体面积CASS9.1中提供了查询指定点坐标、查询两点距离及方位、查询线长、查询实体面积等查询功能,如下图所示: 本文以动画演示的方式,对以上提到的功能进行讲解。 1. 查询指定点坐标 点击【工程应用】…

自定义smokeping告警(邮件+短信)

前段时间接到公司IT同事需求&#xff0c;帮助其配置smokeping的告警功能&#xff0c;之前配置的姿势有些问题&#xff0c;告警有些问题&#xff0c;现在调试OK&#xff0c;在此将关键配置点简单记录下。 关键的配置项主要有&#xff1a; 定义告警规则并配置将告警信息通过管道交…

selenium 定制启动 chrome 的选项

2019独角兽企业重金招聘Python工程师标准>>> selenium 定制启动 chrome 的选项 博客分类&#xff1a; java 搜索引擎&#xff0c;爬虫 使用 selenium 时&#xff0c;我们可能需要对 chrome 做一些特殊的设置&#xff0c;以完成我们期望的浏览器行为&#xff0c;比如…

平台级 SAAS 架构的基础:统一身份管理系统

业内在用户统一身份认证及授权管理领域&#xff0c;主要关注 4 个方面&#xff1a;集中账号管理&#xff08;Account&#xff09;、集中认证管理&#xff08;Authentication&#xff09;、集中授权管理&#xff08;Authorization&#xff09;和集中审计管理&#xff08;Audit&a…

【ArcGIS Pro微课1000例】0017:ArcGIS Pro 2.8制作炫酷的ETOPO1全球DEM地图

ArcGIS Pro相对于ArcGIS,在制图方面做了很大的提升,做出的地图更加优美,本文讲解基于NOAA的ETOPO1数据全球DEM数据制作炫酷的全球DEM地图,先看效果再教学! 1. 效果展示 全球 澳大利亚大陆

Js中的for in

2019独角兽企业重金招聘Python工程师标准>>> 后台数据&#xff1a; List<Map<String, Object>> uTags query.selectAllList("velocity.userGetTags", map); 前端解析&#xff1a; for(var i in data.content){//由于这里是List[i](Map).IDc…

VMWare 安装 Linux

参考 &#xff1a; http://www.aboutyun.com/thread-6780-1-1.html 这的是很详细。赞一下 我这里就简化一下。 1 下载&#xff1a; VMWare : https://download3.vmware.com/software/wkst/file/VMware-workstation-full-10.0.0-1295980.exe ubuntu : http://www.aboutyun…

【ArcGIS Pro微课1000例】0018:ArcGIS Pro 2.8通过OLE DB与个人数据库建立连接案例

对于个人数据库,Access程序可以直接打开。本文讲解在ArcGIS Pro2.8中通过OLE DB与个人数据库MDB建立联系的方法与过程。 文章目录 1. 个人数据库准备2. OLE DB连接个人数据库1. 个人数据库准备 准备一个个人数据库,在上面右键→复制文件地址。 文件地址为:"C:\test.md…

《ASP.NET Core 6框架揭秘》实例演示[02]:基于路由、MVC和gRPC的应用开发

ASP.NET Core可以视为一种底层框架&#xff0c;它为我们构建出了基于管道的请求处理模型&#xff0c;这个管道由一个服务器和多个中间件构成&#xff0c;而与路由相关的EndpointRoutingMiddleware和EndpointMiddleware是两个最为重要的中间件。MVC和gRPC开发框架就建立在路由基…

什么是 JWT -- JSON WEB TOKEN

什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准&#xff08;(RFC 7519).该token被设计为紧凑且安全的&#xff0c;特别适用于分布式站点的单点登录&#xff08;SSO&#xff09;场景。JWT的声明一般被用来在身份提供者和服务提…

【ArcGIS微课1000例】0024:ArcGIS如何连接文件夹、设认工作目录、默认地理数据库、相对路径与绝对路径?

ArcGIS软件在初次安装完成或者为了工作的方便,通常需要连接到指定的文件夹、设置默认工作路径,默认地理数据库、相对路径与绝对路径等。 文章目录 1. 文件夹连接2. 默认工作目录3. 默认地理数据库4. 相对路径与绝对路径1. 文件夹连接 在初次安装完ArcGIS时,默认没有文件夹连…

【Spring Cloud】Redis缓存接入监控、运维平台CacheCloud

CacheCloud CacheCloud提供一个Redis云管理平台&#xff1a;实现多种类型(Redis Standalone、Redis Sentinel、Redis Cluster)自动部署、解决Redis实例碎片化现象、提供完善统计、监控、运维功能、减少运维成本和误操作&#xff0c;提高机器的利用率&#xff0c;提供灵活的伸缩…

[Win10应用开发] 使用 Windows 推送服务 (WNS)

前言 Windows 推送服务&#xff08;WNS&#xff09;也是 Win10 通知机制中的一种&#xff0c;今天与大家一起学习一下有关WNS的相关知识。使用 Windows 推送服务的前提是你需要有一个微软开发者账号&#xff0c;这样才能得到一些合法的密钥信息用于与WNS服务器完成通讯操作。 …

Windows 11 新版 25158 推送!全新搜索框和图标、小组件动态内容和通知标记

面向 Dev 频道的 Windows 预览体验成员&#xff0c;微软现已推送 Windows 11 预览版 Build 25158。主要变化1.微软宣布为 Windows 11 搜索引入全新视觉体验&#xff0c;由搜索框或重新设计的搜索图标呈现。目前该功能仅向部分 Windows 预览体验成员推出&#xff0c;将在未来向所…

【BIM入门实战】Revit创建地形的几种方法及优缺点

Revit在体量和场地选项卡的【地形表面】工具可以创建三维地形,有三种方法:放置点、指定点文件和导入实例文件、倾斜摄影点云技术和InfraWorks地形生成。 文章目录 1. 放置点2. 指定点文件3. 导入实例文件4. 倾斜摄影点云技术5. InfraWorks地形生成1. 放置点 放置点功能位于体…