初探ASP.NET Core 3.x (4) - 项目的重要组成
O 前请提要
在第1期构建项目之后,我们看到项目中有这样的组成结构:
MyASPWebApplication/
├─obj/
│ └─/一些文件.../
├─Pages/
│ ├─Shared/
│ │ ├─_Layout.cshtml
│ │ └─_ValidationScriptsPartial.cshtml
│ ├─Error.cshtml
│ ├─Error.cshtml.cs
│ ├─Index.cshtml
│ ├─Index.cshtml.cs
│ ├─Privacy.cshtml
│ ├─Privacy.cshtml.cs
│ ├─_ViewImports.cshtml
│ └─_ViewStart.cshtml
├─Properties/
│ └─launchSettings.json
├─wwwroot/(empty)
├─appsettings.Development.json
├─appsettings.json
├─Program.cs
├─Startup.cs
└─MyASPWebApplication.csproj
这是初始生成项目所包含的目录结构,这里面包含了很多东西,但是,我们还不清楚每个部分能做些什么,充当怎样的一个角色,以及哪些东西是最基础的,这一节就来解决这些问题。
在第3期中我们从原理层面上了解了ASP.NET Core Web项目的一个基本的工作流程以及所包含的部分。
I 启动部分
I.1 Program
类
按照绝大部分C#开发框架的惯例,Program
类一般是程序的入口,里面包含一个Main
函数并被单独放置于Program.cs
中。
小提示:
尽管C#并不像Java那样强制要求必须将各个数据实体分放到不同的文件中,但C#依然要求定义类的代码文件必须和该文件中所定义的类(如果多个则任取其一)相同。此外,如非必要,我们仍然建议将各个类定义分离到不同的文件中,除非:
class A
与class B
之间有比较大的实体关联,例如依赖关系。class B
只是class A
的一个辅助工具性的类,例如class B
只是针对class A
的一个异常类等
ASP.NET Core产生的Program
类如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace MyASPWebApplication
{
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>();
});
}
}
当然了,这里public static void Main(string[] args)
自然就是程序的入口了。里面调用了他的下一个成员函数:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
这个函数的定义方式很特别,它使用了λ推演(=>
)的方式做的函数定义,但这无关紧要,实际上表达的是:
public static IHostBuilder CreateHostBuilder(string[] args)
{
IHostBuilder _ = Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults
(
webBuilder =>
{
webBuilder.UseStartup<Startup>();
}
)
return _;
}
这个函数主要调用了Host.CreateDefaultBuilder(args)
静态函数使用程序的参数针对服务主机(Host)的进行一个默认配置。综上,Program
类作为程序的入口,其所包含的配置内容大多面向于项目的顶层设施。
关于服务主机(通用主机、Web主机)的相关概念以及配置过程,在以后期介绍。
I.2 Startup
类
在上面的结构中,我们不得不注意的另外项目便是Startup.cs
。当然了,本着C#的开发原则,我们很容易了解到这个代码文件中应当包含一个名为Startup
的类。
Startup
类位于当前项目的命名空间下(也就是说类全称为MyASPWebApplication.Startup
)。这个类在项目被构建时被生成为这个样子:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace MyASPWebApplication
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
}
I.2.1 这个类干什么呢??
如你所见,这个类并不继承于任一个其他的现有类,就是一个非常中规中矩的C#类。但事实上,这个类包含了这个Web应用程序被启动时执行的配置行为。
为什么一个普通的类就可以支配整个项目的配置呢?事实上,尽管被定义为一个普通的类,但Startup
还是有一些特征的。
I.2.2 特征??
很明显,这个类的定义相当简单,除了构造函数外只有两个函数和一个只读属性:
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
这两个成员函数的内容,表明了Startup
的配置主要是两个方面:
ConfigureServices
函数以控制反转(IOC)的方式依赖注入(DI)项目中的 服务、Configure
函数负责配置请求应用管道
这里面出现了很多概念,比如管道、控制反转、依赖注入之类的,这将在之后期中慢慢解释。总之,这些内容是在WebApp运行期间被动态加载的配置。
而这个类最大的玄机在于,它在Program
类中被引用过:
//---- Program.cs(节选) ----//
public static IHostBuilder CreateHostBuilder(string[] args)
{
IHostBuilder _ = Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults
(
webBuilder =>
{
webBuilder.UseStartup<Startup>();
}
)
}
第8行中,webBuilder.UseStartup<Startup>()
就通过webBuilder
加载了这个类作为默认的Host
配置。这也正是为什么Startup
作为一个普通的类就能够作为配置类的原因。
I.3 appsettings.json
终于,我们聚焦于不是C#类的一个主要文件了。这是一个json表格式的配置文件。
当然,我们实际上能看到两个文件:appsettings.json
和appsettings.Development.json
。这两个文件的作用实际上没有什么实质性不同,只是应用的场合并不相同。当项目的运行环境为开发态(Development)的时候,优先使用appsettings.Development.json
,否则使用另外一个。
初始状态下,appsettings.json
的内容为:
{
"Logging":
{
"LogLevel":
{
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
这种文件里配置的内容多和数据有关。比如初始状态下有的"Logging"
子表包含了对日志记录的配置信息。当然,又如当该WebApp使用数据库时,有关数据库的连接信息(连接字符串、超时等)也会被配置到这个json表中。
当然,在这张表中,我们也可以加入自己的配置信息,在程序中可以通过Microsoft.Extensions.Configuration.IConfiguration
对象读取这个文件里的配置信息。
关于此文件中包含的配置项和配置方法也会在以后详细探索。
I.4 launchSettings.json
这个文件存在于Properties
文件夹下,定义了项目启动的配置和属性:
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:64571",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"MyASPWebApplication": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
这个也属于配置文件,主要配置的是项目的运行环境,例如侦听端口等、环境模式、是否运行浏览器以及IIS反向代理的转发端口等。
比如,在这个配置模式下,当项目以IIS Express反向代理的方式运行时,就会访问http://localhost:64571
,但如果不使用IIS而直接使用Kestrel来启动,那么项目地址就是http://localhost:5000
。
II wwwroot
目录
另外一个比较显著的目录是wwwroot
。如果使用Visual Studio创建项目,我们能够看到,这个wwwroot
目录和Properties
目录类似,有个特别的图标:
这里面目前是空的,不过这里一般用于放置静态文件(Static Files)。
II.1 静态文件??
说白了,就是在运行期间不会发生变化的,一般是指资源文件。
比如:静态网页(HTML)、静态样式表(CSS)、静态浏览器脚本(JS)、图片、音频等等等等。
wwwroot
这个目录就是放这些东西进去的。
服务端处理这些静态文件是通过StaticFiles
这一中间件组成的。
// ---- Startup.cs(节选) ---- //
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
第14行就是Startup
类中对此中间件的注册。
III 常规目录
为了使项目更为有序的组织,一些文件会被存放于一些目录(文件夹)中,而这些目录在ASP.NET Core下有约定的名称。
III.1 Pages
目录和Views
目录
Pages
文件夹存放的内容被称为页面,这里用于存放Razor Pages开发模式下的Razor页面。
Razor Pages是ASP.NET Core 2.x中提出的一种新的开发模式,聚焦于页面,并以页面为中心。
在MVC开发模式下,这个部分被替换为视图,存放于Views
文件夹中。尽管两者之间有所不同,但我们目前要知道的是,这个文件夹里的东西聚焦于前端。
III.1.1 页面??但是wwwroot
里不是也存放页面了么??
是的,但是wwwroot
里面存放的是静态页面,而这里面存放的页面被称为Razor页面,是一种动态页面。
III.1.2 有什么区别么??
当然有区别。虽然说这两者到浏览器客户端都会被解析为同样的东西,但是对于服务端而言,这两者有很大的区别。
存放于wwwroot
中的页面简单来说就是写死的页面,这种页面无论运行多少次,服务端数据如何变化,其页面结果是不变的。
但动态页面是不一样的,动态页面是保留了一定不变内容基础之上又根据后端数据变化而被重新生成的。在浏览器上就被体现为,随着后台数据的变化,页面的显示结果会有所不同。
III.1.3 Shared
子文件夹
无论是Pages
还是Views
,因为都存放带有Razor标记的页面,所以这两个目录下往往还有一个子目录,称为Shared
。这个目录主要存放的是共享的分部标记元素(我知道听不明白,以后会解释的)。
III.1.4 PageModels
文件
如果您足够细心的话,会发现对应于每一个Razor页面,实际上还有一个与之同名的".cs"文件。这些文件里面大致长这样(以Index为例):
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace ContosoPizza.Pages;
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
这个实际上就是PageModel了,它主要是从这些Razor页面中分离出逻辑用的,PageModels封装数据属性和逻辑操作,使其作用域仅限于其 Razor 页面。
III.2 Controller
文件夹
这个文件夹仅存在于MVC模式下,存放MVC模式当中的控制器(Controller,MVC中的C)。
控制器是一种特殊的类,ASP.NET Core中约定控制器类以Controller
结尾。
在MVC中,控制器主要用于处理请求(Request)和路由(Routing)并提供响应。作用有些类似于Java中的Servlet。(具体内容和其他概念的以后介绍,以后介绍)
III.3 Models
文件夹
如果WebApp关联了数据库,那么数据库中的数据必然存在一种数据模型,这种数据模型在关系理论中称为关系模式,它实际上与面向对象理论中的类是对应的。
一般来说因为数据库的数据组织方式和应用中组织方式的这种不同,因此在项目中处理这些数据就必须自己编写数据访问(DA)功能将数据库中的关系元组转化成应用可以使用的类对象,但是一个Web项目中的数据门类非常的多,这也就造成了关系模式也非常的多,要编写的内容也就非常的多,那么为了统一地、自动地处理和显示这些数据,减轻重复编码的负担,一种称为对象关系映射(ORM)的数据开发模式就产生了。
ORM的机制使得我们只需要在程序中写出数据模型(类定义),而无需提供存取方法(这个由ORM提供,这个过程也被称为数据绑定)。那么Models
目录就是为了存放这些数据模型的。
IV 其他
除了上面这些之外,还有其他的东西,遇到再说。
To be continued ...