原文:ASP.NET Core - Getting Started with ASP.NET Core 2.0
ASP.NET Core 能够创建快速的,便捷的,跨平台的web应用。这篇文章通过一个简单的ASP.NET Core web站点,向你展示工程中的每个文件的作用,同时也将阐述ASP.NET Core的一些重要概念。还将特别关注ASP.NET Core 2.0 的改变,以帮助熟悉ASP.NET Core 1.0 和 1.1 的读者过度到2.0。
创建ASP.NET Core 工程
ASP.NET Core 工程可以通过Visual Studio模板或.NET Core 命令行接口(.NET CLI)创建。Visual Studio 2017有极好的 .NET Core 开发体验(包括顶级的代码调试、Docker集成以及其他功能),但是在这里,我将使用.NET Core命令行和Visual Studio Code,以防有些读者使用Mac或Linux的开发机。
dotnet new 命令用来创建新的.NET Core工程。当运行 dotnet new 而不加参数,命令行会列出可以使用的工程模板,如图1所示。如果你熟悉2.0版本之前的.NET CLI,你将会注意到2.0加入了一些新的工程模板。
Angular和React.js SPA模板: 将创建一个ASP.NET Core 的应用程序,为前端的SPA程序(使用Angular 4 或 React.js)提供服务。这些模板包括前端和后端的程序,以及用于构建前端的Webpack的配置。
ASP.NET Core Web App (Razor Pages): Razor页面是ASP.NET Core 2.0的一个新特性,它能够创建直接处理请求的页面,而不需要Controller。它非常适合基于页面的程序模板。
让我们用 dotnet new razor 命令创建一个Razor页面的工程。当这个工程被创建后,你可以执行 dotnet run 命令运行它。在.NET Core 之前的版本,需要执行先 dotnet restore 命令来安装必须的NuGet包。但是从.NET Core 2.0 开始,使用CLI命令时,restore命令会自动执行。运行这个站点并输入app的URL地址(如: http://localhost:5000 ),你将看到这个web app(图2)。
恭喜你完成了第一个ASP.NET Core 2.0 的一个App!现在你运行了一个简单的web app,让我们看一看这个工程的内容,以便你更了解ASP.NET Core是怎样工作的。
依赖,源文件和资源
第一个要看的是工程文件本身----.csproj文件。这个文件告诉MSBuild怎样构建这个工程,依赖的包,.NET Core 的目标版本等等。如果你看过以前版本的.csproj文件,你会发现2.0版本的这个文件非常小。社区做了很多的努力,使得.csproj文件简短且已读。一个显著的改变是,源文件不在显示的被列在工程文件中。取而代之的是.NET Core 会自动编译所有.cs文件。同样,任何.resx文件都作为资源而被包含。如果你不希望所有的.cs文件都被编译,您可以从Compile ItemGroup中删除它们,也可以通过将EnableDefaultCompileItems属性设置为false来完全禁用默认编译项。
app运行的 .NET 版本由 <TargetFramework>
元素指定。这里为了使用ASP.NET Core 2.0的新特性,设置为了netcoreapp2.0。
.csproj文件中的 <PackageReference>
元素表示工程中依赖的NuGet包。在ASP.NET Core 2.0 中,你会注意到它默认只包含了一个数据(Microsoft.AspNetCore.All)。这个package包含了所有其它的Microsoft.AspNetCore的包,这样使得ASP.NET Core 2.0 的工程文件比起之前的短小了很多。其它的NuGet依赖包可以通过Visual Studio NuGet Package插件的管理界面或者.NET CLI 中 dotnet add 命令添加<PackageReference>
元素。
如果你使用了SPA模板(angular,react或reactredux),还会在.csproj文件中定义标签以确保在构建工程的时候同时运行Webpack。
创建和运行Web Host
Program.cs 是整个程序的入口点。ASP.NET Core 的应用是一个console application,像所有的控制台应用一样,当app运行时,会运行Main方法。
在ASP.NET Core 2.0模板中,Main方法相当简单,就是创建了一个IWebHost对象,然后调用了Run方法。
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
如果你以前使用过1.0版本,你会注意到现在这个文件更简单了。原因是WebHost.CreateDefaultBuilder这个新方法。以前,ASP.NET Core 应用程序的Main方法为了创建IWebHost的实例需要配置 WebHostBuilder,这个配置包含了一些步骤,像指定web server,设置content root路径,以及运行IIS集成等。
CreateDefaultBuilder创建了一个默认配置的IWebHost,以简化以上的步骤。除了前面指定的项,CreateDefaultBuilder还做了一些在以前版本中Startup.cs类中设置的事(设置配置信息,注册默认的logging providers)。因为ASP.NET Core是开源的,如果你感兴趣,你可以在GitHub WebHost.cs上看到CreateDefaultBuilder做的所有细节。
让我们简要的看一看在CreateDefaultBuilder中重要的调用以及其目的。虽然CreateDefaultBuilder已经为你做了所有的工作,但是理解幕后发生了什么总是好的。
public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional:true, reloadOnChange: true);
if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseIISIntegration()
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
});
return builder;
}
UserKestrel()
方法指定你的程序将使用Kestrel作为web server(基于libuv,跨平台),在web server上还有其他选项,如HttpSys(UseHttpSys),HttpSys只支持windows,但是有允许Windows身份认证的优点,可以安全的直接暴露在Internet上。而Kestrel需要放在像IIS,Nginx或Apache这些反向代理后面接收请求。
UseContentRoot()
方法指定了应用程序的根目录,ASP.NET Core 能够找到整个站点的内容,比如config文件。需要注意的是这个根目录不是Web root(静态文件存放的地方),虽然默认的Web root 是基于内容根目录的([ContentRoot]/wwwroot)。
ConfigureAppConfiguration
方法创建了configuration对象,应用程序在运行时通过它来读取配置。在CreateDefaultBuilder方法中,它将读取appsetting.json,appsetting.[environment].json,环境变量以及命令行参数中的配置,如果是开发环境,它还将使用user secrets。这个方法在ASP.NET Core 2.0 是新增的,我们将在后续讨论更多的细节。
ConfigureLogging
设置应用程序的日志。在CreateDefaultBuilder中,添加了控制台日志和debug日志。像ConfigureAppConfiguration一样,这个方法也是ASP.NET Core 2.0中新加的,我们将稍后讨论。
UseIISIntegration
配置了应用程序运行在IIS中。注意,UseKestrel依然是需要的。IIS作为反向代理而Kestrel作为宿主。如果app没有运行在IIS后,UseIISIntegration
方法也不会产生负面影响,所以即使app运行在非IIS的场景下,这个调用也是安全的。
多数情况下,CreateDefaultBuilder提供的默认配置是足够的。如果需要默认配置以外的配置,可以在app的启动类中指定。调用UseStartup<T>方法时,T就是启动类。
如果CreateDefaultBuilder没有满足你的场景的需求,你可以自定义的创建IWebHost。如果你只需要小的改动,你可以修改调用CreateDefaultBuilder方法返回的WebHostBuilder(如,再次调用ConfigureAppConfiguration,添加更多的配置源)。如果你需要做大量的修改,你可以跳过CreateDefaultBuilder方法,像ASP.NET Core 1.0 或 1.1那样,构造你自己的WebHostBuilder。即使你这样做,你仍然可以使用ConfigureAppConfiguration和ConfigurLogging方法的优点。Web Host配置更多的细节可以在这里获取Web 主机
ASP.NET Core 环境
CreateDefaultBuilder的几个操作依赖于ASP.NET Core运行的环境,环境(Environment)在2.0中不是新的概念,但是还是值得简要的回顾一下,因为它会频繁的出现。
在ASP.NET Core中,app运行的环境被指定为ASPNETCORE_ENVIRONMENT
变量。你可以设置任何你喜欢的值,但是Development,Staging和Production是典型的值。所以,在调用 dotnet run 命令之前设置了 ASPNETCORE_ENVIRONMENT
变量的值为Development(或者,你在launchSettings.json文件中设置了environment变量值),你的app将运行在Development模式下(如果用Production代替了Development,默认将没有变量集)。有多个ASP.NET Core的功能通过使用这个变量来修改运行时的行为(比如Configuration和Logging),你也可以在你自己的代码中通过IHostingEnvironment服务来访问这个值。更多的信息可以在ASP.NET Core 文档中获取。
ASP.NET Core 配置
ASP.NET Core使用Microsoft.Extensions.Configuration包的IConfiguration接口提供运行时的配置。前面已提到,CreateDefaultBuilder将会从.json文件和环境变量中读取设置。配置系统是可扩展的,虽然它能够从各种提供者(json,xml,ini,environment...)中读取配置信息。
当使用IConfiguration和IConfigurationBuilder对象添加配置时,记住配置的顺序很重要。后面的配置的设置会覆盖前面的,所以你要先添加公共基础的配置,然后在添加特定环境的配置,这样后者才可能覆盖前者。
配置设置在ASP.NET Core 中是分层级的。当你创建新项目时,比如,appsetting.json包含了顶层的Logging元素,其下有子设置项。这些设置表示消息日志的最低优先级(LoggingLevel项),以及当消息来时是否在app的逻辑范围从而被记录(变量IncludeScopes)。要获取这些嵌套设置,你可以使用IConfiguration.GetSection()方法获取单个的配置节,或者指定特定配置的使用冒号做分隔符的全路径。所以,IncludeScopes在项目中的值可以这样获取:Configuration["Logging:IncludeScopes"]
。
//ASP.NET Core 的配置文件appsetting.json
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
使用环境变量定义配置设置时,环境变量的名字需要包含所有层级,可以使用冒号(:)或双下划线(__)来分隔。比如,命名为Logging__IncludeScopes的环境变量将覆盖上面appsetting.json中IncludeScopes的设置,前提是,环境变量在appsetting后添加,CreateDefaultBuilder就是这样设计的,config.AddEnvironmentVariables();
在config.AddJsonFile()
后添加。
因为 WebHost.CreateDefaultBuilder从appsetting.json和app.{environment}.json文件中读取配置,你会注意到当你改变环境时,日志的行为也改变了(appsettings.Development.json覆盖了appsettings.json中LogLevel的配置)。当设置了环境变量为Development,在调用dotnet run 后,你会注意到控制台记录了很多的日志(对调试有好处),而如果你设置了环境为Production,那么除了警告和错误信息,你看不到任何的日志(这样做是因为在生产环境下,应该尽可能少的记录日志)。
如果你有过ASP.NET Core 1.0和1.1的经验。你会注意到ConfigureAppConfiguration方法是2.0新增的。以前,常用的做法是在创建启动类时创建IConfiguration。使用ConfigureAppConfiguration方法来替代以前的做法是非常好的,因为他将IConfiguration对象存储在了app的DI容器中,以方便以后获取,以及在app的生命周期中提前了变量设置。
ASP.NET Core 日志
与装载配置一样,如果你熟悉以前的版本,你会记得日志装载也在Startup中,在2.0中,日志装载在IWebHost的ConfigureLogging方法中完成。
它仍然可以在Startup中装载(在Startup.ConfigureServices方法中使用services.Add-Logging),但是,在Web Host创建时配置日志,简化了Startup类,甚至可以在应用程序启动过程中更早地进行日志记录。
与配置一样,ASP.NET Core日志记录也可以扩展。注册不同的配置以记录到不同的终端。很多配置都及时有效的使用Microsoft.AspNetCore.All库。
从WebHost.CreateDefaultBuilder的源代码可以看出,在ILoggingBuilder上调用指定的提供者扩展方法可以添加日志提供者(如AddDebug或AddConsole)。如果你使用了WebHost.CreateDefaultBuilder,但是还想注册其他的日志提供者,可以在CreateDefaultBuilder返回的IWebHostBuilder上调用ConfigureLogging方法。
一旦提供者已被注册和日志已被配置。ASP.NET Core会自动记录传入的请求消息,你也可以从DI容器中获取ILogger对象,调用ILogger.Log()方法,记录自己的消息。
启动类型
现在你看到了Program.cs 是怎样创建Web host的,现在让我们跳到Startup.cs文件中。app将要使用的startup,是在创建IWebHost时调用StartUp方法是指定的。ASP.NET Core 2.0中Startup.cs没有太多的改变(除了将logging和configuration移动到Program.cs文件中实现),但是我将简要的回顾一下Startup类中两个重要的方法,因为它们对ASP.NET Core应用程序非常重要。
Startup类的ConfigureServices方法通过依赖注入将服务添加到app的DI容器中,所有的ASP.NET Core的应用都有默认的DI容器去存储服务,方面后面使用。DI容器使得服务变得可用,你已经看过了一对例子,ConfigureAppConfiguration和ConfigureLogging将在你的app中添加服务到容器,以方便后续使用。在运行时,如果一个这样的类型的实例被调用,ASP.NET Core 将自动从DI容器中获取对应的对象。
举个例,ASP.NET Core 2.0 程序的Startup类有一个构造函数,他有一个IConfiguration参数,当IWebHost开始运行时,这个构造函数将会自动被调用。这时,ASP.NET Core 将会从DI容器中提供IConfiguration参数给构造函数。
另一个例子,如果你想从Razor页面记录消息,你可以在页面模型的构造函数中将logger对象作为参数向App请求(像Startup请求IConfiguration对象一样),或者在cshtml中使用@inject语法,如下:
@using Microsoft.Extensions.Logging
@inject ILogger<Index_Page> logger
@functions {
public void OnGet()
{
logger.LogInformation("Beginning GET");
}
}
同样可以来获取IConfiguration对象或任何其他已注册的类型。用DI容器这种方法,可使Startup类,Razor页面,controller等与所依赖的服务解耦。
在这节开始提到,在Startup.ConfigureServices方法中,服务被添加到DI容器。你使用程序模块创建app时,已经有一个服务在ConfigureServices方法中被注册了:services.AddMvc。就像你猜的一样,这个注册的服务需要MVC框架。
另一个常见的服务是Entity Framework Core,尽管他不会再本例中使用,使用Entity Framework Core 的应用程序通常使用services.AddDbContext()方法,注册所需的DBContexts,以保证他们使用Entity Framework模型。
More details on dependency injection in ASP.NET Core are available at bit.ly/2w7XtJI.
你也可以使用services.AddTransient(),services.AddScoped() 或 services.AddSingleton()方法注册你自己的类型和服务。注册singleton将在每次请求时返回一个单列,注册一个transient将返回在每次请求的时候返回一个新的实例。注册scoped将在一个独立的http请求线程中返回一个单列,有关ASP.NET Core中依赖项注入的更多详细信息,请参见这里
http请求--处理管道和中间件
Startup类中另一个重要的方法是Configure。这是ASP.NET Core应用程序的心脏,http请求管道在这里被装载。在这个方法中不同的中间件被注册,这些中间件将处理http请求,并生成响应。
在Startup.Configure方法中,中间件被添加到IApplicationBuilder中形成处理管道。当请求进入时,第一注册的中间件被调用,这个中间件将执行它的逻辑,然后调用下一个中间件,如果它完成了它的处理逻辑,将返回上一个中间件。在图4中示出了在请求到达之后按顺序调用中间件组件的模式,然后在处理之后以相反的顺序调用中间件组件。
举一个具体的例子,图5显示了模板工程的Configure方法,当新请求进来时,它将首先访问DeveloperExceptionPage中间件或ExceptionHandler中间件,这取决你的ASPNETCORE_ENVIRONMENT的环境变量。这些中间件最初不会做过多的操作,但是在后续的中间调用并退出中间件管道后,他们将关注异常的处理。
//图5 ASP.NET Core Startup.Configure 方法装载中间件管道
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
接下来,StaticFiles中间件将会被调用,它可以为请求提供静态文件(图片或样式等)。如果他执行了,他将停止管道并返回上一个中间件(异常处理中间件)。如果StaticFiles中间件不能提供响应,他将调用下一个中间件--MVC中间件,MVC中间件会尝试路由这个请求到MVC控制器(或者Razor页面)。
中间件组件的注册顺序非常重要。如果UseStaticFiles位于UseMvc之后,应用程序将尝试在检查静态文件之前将所有请求路由到MVC控制器,这可能会导致明显的性能下降。如果异常处理中间件在管道后面出现,它将无法处理先前中间件组件中发生的异常。
Razor页面
除了.csproj,program.cs和startup.cs文件之外,你的ASP.NET Core项目还包含一个Pages文件夹,其中包含应用程序的Razor页面。Razor页面类似于MVC视图,但是请求可以直接路由到Razor页面而无需单独的控制器。这样可以简化基于页面的应用程序并将视图和视图模型保持在一起。支持页面的模型可以直接包含在cshtml页面中,或者在单独的代码文件中,它与@model指令一起引用。要了解Razor Pages的更多信息,可以查看Steve Smith的文章“Simpler ASP.NET MVC Apps with Razor Pages”