ASP.NET 与 ASP.NET Core 很类似, 但它们之间存在一些细微区别以及 ASP.NET Core 中新增特性的使用方法, 在此之前也写过一篇简单的对比文章 ASP.NET MVC 应用迁移到 ASP.NET Core 及其异同简介, 但没有进行深入的分析和介绍, 在真正使用 ASP.NET Core 进行开发时, 如果忽略这些细节可能会出现奇怪的问题, 特此将这些细节进行分享.
本文主要内容有:
无处不在的依赖注入(Dependency Injection, DI)
服务的注册
替换默认的依赖注入容器
将 Controller 注册为服务
服务的获取
常用的服务
Configuration&Options
ASP.NET Core 请求管道建立
ASP.NET Core Mvc
路由
控制器
Area
View
模型绑定
Action 的返回值与 JSON 序列化
静态资源
web API
API 控制器的创建
Action 参数的绑定
Signalr
小结
注: 本文基于 ASP.NET Core 2.1 版本,.Net Core SDK 版本需要 2.1.401+. 长篇预警( ╯□╰ )
无处不在的依赖注入
ASP.NET 与 ASP.NET Core 之间最大区别之一就是内置了依赖注入机制, 虽然 ASP.NET 中也有 DI 机制, 但没有内置容器, 一般都需要使用第三方的容器来提供服务, 另外依赖注入的概念也不像 ASP.NET Core 中这样无处不在.
简单来说依赖注入的目的是为了让代码解耦以提高代码的可维护性, 同时也要求代码设计符合依赖导致原则使得代码更加灵活, 而其原理实际上就是在应用程序中添加一个对象容器, 在应用初始化时将实际的服务 "放" 到容器中, 然后当需要相应服务时从容器中获取, 由容器来组装服务.
服务的注册
ASP.NET Core 的 Startup(注: Startup 仅仅只是约定名称, 实际使用是在 Program 类型中创建 WebHost 时使用的), 该类型中包含两个方法分别是 ConfigureServices 和 Configure, 其中 ConfigureServices 的主要作用就是用来将服务 "放" 置到容器中
代码来自:
替换默认的依赖注入容器
ASP.NET Core 的默认容器仅提供了构造注入功能, 如果需要使用属性注入等功能或者在迁移时原有应用依赖于其它容器, 那么可以通过使用第三方容器实现.
将默认容器替换为其它容器仅需三步:
1. 将 ConfigureServices 方法的返回类型改为 IServiceProvider.
2. 将 ASP.NET Core 中的服务注册到第三方容器中.
3. 使用第三方容器实现 IServiceProvider 接口并返回.
官方文档以 Autofac 为例, Autofac 已经实现了 ASP.NET Core 服务注册到 Autofac 容器中, 以及 Autofac 容器的 IServiceProvider 接口封装, 仅需安装 Autofac 以及 Autofac.Extensions.DependencyInjection 包即可.
详情参考:
使用 windsor 或其它容器可以参考:
将 Controller 注册为服务
虽然 Controller 在激活时是通过容器来获取 Controller 的依赖 (即构造方法需要的参数), 在代码运行的时候给人一种 Controller 是从容器中组装的错觉, 但是实际上默认情况下 Controller 的组装过程不是直接由容器组装的, 如果要让 Controller 从容器组装, 那么在配置 MVC 服务时需要通过. AddControllerAsServices() 方法将 Controller 注册到容器中:
注: 一般情况下是否将 Controller 注册为服务对 Controller 的开发和代码的运行并没有很大区别, 但是如果当容器变更为其它容器, 并且使用了容器提供的如属性注入等功能时, 如果没有将 Controller 注册为服务, 那么相应的属性注入的过程也不会被触发, 简单来说就是只有将 Controller 注册为服务, 那么实例化 Controller 的工作才会由容器完成, 才会触发或者使用到容器提供的其它特性.
服务的获取
前面介绍了服务的注册, 现在来介绍一下在 ASP.NET Core 中有哪些方法可以获取服务:
1. Controller 构造方法参数.
2. 通过 Controller 注入 IServiceProvider 类型, 通过 IServiceProvider 来获取服务:
3. 在 Action 方法或者 Mvc 过滤器 (过滤器的上下文参数中包含 HttpContext) 中通过 HttpContext 的 RequestServices 对象获取服务:
4. 在 View 上通过 @inject 注入服务:
5. 在 Action 方法中, 通过 FormServices 特性注入:
注: 一般来说尽可能显式的标明类型的依赖(即通过构造参数的方式声明当前类型所依赖的组件), 上面的 2 和 3 两点分别都是通过服务提供器在方法内部来获取依赖, 这样做依赖对于外界来说是不可知的, 可能会对代码的可维护, 可测试性等造成一定影响, 这种模式被称为 Service Locator 模式, 在开发过程中尽可能避免 Service Locator 模式的使用.
常用的服务
ASP.NET Core 相对于 ASP.NET 来说取消了一些常用的静态类型, 比如 HttpContext,ConfigurationManager 等, 取而代之的是通过将类似的组件以服务的形式注册到容器中, 使用时通过容易来获取相应的服务组件, 这些常用的服务有:
1. IHostingEnvironment: 包含了环境名称, 应用名称以及当前应用程序所处的根目录及 Web 静态内容的根目录(默认 wwwroot).
2. IHttpContextAccessor: 从名字可以看出, 它用来访问当前请求的 HttpContext.
3. IConfiguration:ASP.NET Core 配置信息对象.
4. IServiceProvider: ASP.NET Core 服务提供器.
5. DbContext: 这里的 DbContext 指的是 EFCore 的 DbContext, 在 ASP.NET Core 中, EFCore 的 DbContext 也是在 ConfigureServices 方法中进行配置并添加到容器, 使用时直接从容器中获取(但要注意的是对于分层结构的开发风格来说, DbContext 不会直接被 Controller 依赖, 而是被 Controller 中依赖的业务服务类型所以来, 就是说编写 Controller 代码的时候不会直接与 DbContext 发生直接交互).
Configuration&Options
在 ASP.NET 的开发中, 通常某个变量需要从配置文件读取, 一般都是在相应类型的构造方法中, 通过静态类型 ConfigurationManager 的 AppSettings 方法来读取并初始化变量. 虽然 ASP.NET Core 也可以在类型中注入 IConfiguration 实例来直接读取配置文件, 但该方法由于 Options 模式的出现已经不再建议使用, 使用组件通过依赖相应的组件 Options 可以做到关注点分离, 提高程序的灵活性, 可拓展性, Options 使用方法见文档:
ASP.NET Core 请求管道建立
ASP.NET 由于是基于 IIS 请求管道的, ASP.NET 应用程序仅仅是管道中的一个处理环节, 管道中还包含如身份验证, 静态文件处理等环节, 但 ASP.NET Core 不一样, 它脱离了 IIS 处理管道, 所以整个管道的建立均需要靠程序自身完成, 而 ASP.NET Core 建立管道的代码就是 Startup 类型的 Configure 方法, 该方法通过 IApplicationBuilder 实例来添加不同功能的中间件, 通过中间件的串联形成处理管道, 下图是 ASP.NET Mvc 模板生成的管道代码:
图片来自:
该管道主要包含了错误处理 (开发环境显示异常信息, 其它环境跳转错误页面) 中间件, 静态文件处理中间件以及 Mvc 中间件.
更多中间件可参考文档:
ASP.NET Core Mvc
ASP.NET Core Mvc 与 ASP.NET Mvc 相比整体上区别不大, 但仍然有很多细节上的变化, 下面就开始一一介绍:
路由
路由的作用是将请求根据 Url 映射到 "对应" 的处理器上, 在 Mvc 中请求的终点就是 Controller 的 Action 方法, 而这里所谓的 "对应" 指的是 Url 与路由模板的匹配, ASP.NET Core Mvc 通过以下的方式添加路由模板:
上图中的路由模板是最常用的路由模板, 使用花括号内的内容为路由参数及其默认值, Url 中通过路由参数控制器名称, 活动方法名称来匹配到相应控制器的活动方法.
在注册路由时可以为相应路由添加默认值, 路由参数约束以及对应路由的相关附加数据(datatokens):
路由的功能除了处理请求匹配外, 还具有链接生成的功能, 特别是 Mvc 程序的 View 中使用 IUrlHelper 或 TagHelper 来生成页面的超链接:
其生成原理是通过链接参数 (如上图所示的 Controller 和 Action) 去路由表中匹配, 然后使用匹配结果中的第一个路由 (可能会匹配到多个路由对象, 具体内容在后续 Area 章节介绍) 来生成链接.
更多路由信息及路由模板定义参考文档:
控制器
ASP.NET Core Mvc 的 Controller 一般继承 Controller 类型实现, 基类 Controller 中包含了 Mvc 中常用的返回方法 (如 JSON 以及 View 等) 以及用于数据存储的 ViewBag,ViewData,TempData.
Area
Area 是 Mvc 应用中用来进行功能拆分或分组的一种方式, Area 一般有自己的命名空间和目录结构, 一般 Area 的默认目录结构如下:
ASP.NET Core Mvc 和 ASP.NET Mvc 中的概念和用法基本上是一致的, 但也存在一些区别:
1. Area 下面的 Controller 需要使用 Area 特性标明当前 Controller 属于哪一个 Area:
注: Area 的目录结构不是必须的, 只需要通过特性标记的 Controller 都会被正确识别, 但目录结构的改变会导致无法找到 View, 关于 View 的查找路径会在后续介绍.
2. Area 的路由注册也是在 UseMvc 方法中完成:
注: 携带 Area 的路由模板需要放在前面, 否则在生成通过 IUrlHelper 或 TagHelper 生成链接时, 由于 Controller 以及 action 会匹配到没有 area 的模板并使用该模板生成链接, 导致 area 参数被忽略, 而生成类似:/controller/action?area=area 的结果(在生成 Url 时, ASP.NET Core 会将多余的路由参数放置到查询字符串中)
View
View 是基于 Razor 的 html 模板, Razor 的详细语法参考文档:
ASP.NET Core Mvc 的 View 与 ASP.NET Mvc 中的使用方法基本一致, 主要区别如下:
1. 引入了 TagHelper, 使用 TagHelper 可以让 View 的代码更接近 HTML. 更多 TagHelper 信息参考文档:
2. Controller 将参数传输到 View 的方法添加了 ViewData 特性, 使用方法如下:
View 中访问被 ViewData 标记的方式:
更多详情参考文档:
3. 新增 View 组件:
配置 View 的查找路径:
ASP.NET Core 可以在 ConfigureServices 方法中对 RazorViewEngineOptions 进行配置, 如下图所示, 在默认查找位置基础上添加了 View 以及 AreaView 的查找路径:
模型绑定
模型绑定指的是 ASP.NET Core Mvc 将请求携带的数据绑定到 Action 参数的过程, ASP.NET Core Mvc 的模型绑定数据源默认使用 Form Values,Route Values 以及 Query Strings, 所有值都以 Name-Value 的形式存在, 模型绑定时主要通过参数名称, 参数名称. 属性名称, 参数名称 [索引] 等方式与数据源的 Name 进行匹配.
除了默认的数据源之外还可以从 Http 请求 Header,Http 请求 Body 甚至从依赖注入容器中获取数据, 要从这些数据源中获取数据需要在相应参数上使用 [FromHeader],[FromBody],[FromServices] 特性.
如果需要获取的数据在不同数据源中都存在时 (Name 存在于多个数据源中), 还可以通过特性指明从哪一个数据源中获取, 如[FromForm],[FromQuery] 及[FromRoute].
需要注意的是 [FromBody] 默认只支持 JSON 格式的内容, 如果需要支持其它格式, 如 xml 需要添加相应的格式化器, 添加方法如下图所示:
更多模型绑定及验证内容请参考文档:
其中模型验证的使用方式与 ASP.NET Mvc 一致, 仍然是通过相应的验证特性对模型或模型属性进行标记.
Action 的返回值与 JSON 序列化
说完 Action 方法参数的绑定, 再来看一下 Action 方法的返回类型, 在 ASP.NET Mvc 中 Controller 提供了返回页面内容的 View 方法以及返回 JSON 内容的 JSON 方法(当然还有文件, 重定向, 404 等等其它内容返回方法, 详见 Controller 与 ControllerBase 类型).
这里有一个需要注意的地方是当使用 JSON 方法返回一个对象实例时, 默认使用首字母小写的驼峰命名方式序列化实例的属性名称, 如下图所示:
访问结果:
要使用大写驼峰形式命名需要在配置 Mvc 服务时添加以下代码来修改 JSON 默认的序列化配置:
注: 同样的问题也存在于 WebAPI 的 Ok 方法以及 Signalr 的 JSON 格式协议.
静态资源
由于 ASP.NET Core 已经不再使用 IIS 请求管道, 所以对于静态资源的访问来说需要在请求管道中添加相应的处理中间件来完成:
默认的无参 UseStaticFiles 方法将 wwwroot 目录作为静态资源存放目录, 如果要添加其它静态内容目录可以再次使用 UseStaticFiles 方法, 并通过 StaticFileOptions 对目录的访问路径以及实际路径进行配置:
注: 由于 ASP.NET Core 可以在 Linux 下运行, 所以对于 Linux 来说路径是大小写敏感的, 另外由于 Windows 和 Linux 类系统的路径分隔符也不一致, 所以为了保证路径的统一, 可以使用 Path.Combine 方法, 该方法会根据操作系统的不同对路径进行不同的处理.
另外对于 CSS 及 JS 资源文件的打包, 压缩功能, 最新版本 (ASP.NET Core 2.1) 的应用模板以及不会自动添加相关功能, 需要在拓展工具中添加 Bunlder& Minifier 拓展:
然后通过右键 JS 等资源文件来创建 bundleconfig.JSON 文件:
WebAPI
API 控制器的创建
ASP.NET Core 将 Mvc 和 WebAPI 进行了合并, 它们的实现都直接或间接继承了 ControllerBase 类型, 只不过 Mvc 的基类 Controller 在 ControllerBase 的基础上添加了一些用于处理 View 的功能.
用 ASP.NET Core 开发 WebAPI 时, Controller 类型直接继承 ControllerBase. 然后这个 API 的 Controller 就具有了基类的特性, 返回一个结果仅需要使用 Ok 方法即可, 如下图所示:
然后在路由表中添加路由:
即可通过 / API/default/index 访问到这个 API:
但对于 REST 风格的 API 来说, 它需要通过 ApiController 特性对 Controller 类型进行标记, 并且通过 Route 特性来设置路由:
然后就可以通过 HTTP 谓词来访问 API:
但要注意的是在 ASP.NET Core 中实现的 REST 风格的 Controller, 它不会再根据 action 方法的名称来匹配谓词, 所以存在多个方法时会, 那怕对方法进行了命名, 但仍然会出现以下错误:
为了解决这个问题, 需要通过添加谓词特性解决:
模型绑定
WebAPI 中的模型绑定与 MVC 存在一些区别, 首先当使用 ApiController 标记 Controller 类型时, 如果模型绑定验证未通过, 会直接返回 400 错误, 不会执行 Action 方法(免去了使用! ModelState.IsValid 进行判断):
执行结果:
其次使用 ApiController 标记的 Controller 在执行模型绑定时会使用默认的推断规则, 该规则分别从 Body,Form,Header,Query,Route,Services(它们分别对应 FromBody,FromForm,FromHeader,FromQuery,FromRoute,FromServices 特性)中推断获取数据并绑定, 为什么说推断?
因为有一些特殊的规则:
1. FromBody 用于复杂类型推断, 如果不是复杂类型 (如 int,string 等) 以及特殊的内置类型 (IFormCollection 文档例子), 则不会从 Body 中获取数据, 除非通过[FromBody] 特性指明, 例子如下:
请求结果:
当使用 [FormBody] 指明参数数据源后可以正常访问:
注: 当请求参数为简单类型时, 请求体内容类型需要为 application/JSON, 内容不能为 JSON 字符串, 使用参数值作为内容即可(上图 id 没有提供的异常并不是因为 JSON 格式问题, 而是没有指明从 body 中获取数据导致的).
2. 只能存在一个参数从 Body 中获取数据, 如果出现多个参数时, 只能保证一个参数从 Body 中获取数据, 其它参数需要指明获取数据的位置:
该 API 的调用方式如下:
3. FromForm 默认只推断文件 (IFormFile) 及文件集合类型(IFormFileCollection), 其余类型默认均不会从 Form 中获取.
4. 使用 FromForm 特性时会推断 multipart/form-data 请求内容类型.
以上推断行为可以通过如下配置禁用:
更多信息参考文档:
SignalR
SignalR 是用于客户端服务器实时通信的工具库, 从 ASP.NET 中就具有该功能, ASP.NET Core 中的 SignalR 概念与用法与原来基本一致, 但也存在一些区别:
1. 支持更多的客户端,.Net 客户端, Java 客户端, JS 客户端以及非官方的 C++ 客户端, Swift 客户端.
2. 当链接 SignalR 并通过身份验证后, SignalR 会保存当前用户链接 SignalR 的 ID 以及通过验证后的用户名, 可以通过用户名向用户客户端推送消息.
3. 在应用程序中可以通过 IHubContext<HubType > 方式, 对 SignalR 上下文进行注入, 并且可以直接通过该上下文推送数据给已经链接的客户端, IHubContext<HubType > 实际上是 GlobalHost.ConnectionManager.GetHubContext<HubType>()的替代方式.
4. ASP.NET Core 中通过 App.UserSignalR 以及 route 参数来映射一个 Hub, 每一个 Hub 拥有独立的上下文, 因此如果要使用 IHubContext<HubType > 来向客户端推送信息, 那么必须准确注明 Hub 的类型, 如下图代码应该使用 IHubContext<ChatHub>, 不能使用除 ChatHub 以外的类型(基类也不行).
5. SignalR 默认使用 JSON 协议传输数据, 默认情况下使用首字母小写的驼峰命名方式序列化对象, 要更改该默认行为需要通过一下代码, 替换默认的序列化行为:
6. ASP.NET Core 的客户端代码 (特指 JS 客户端) 有变更, 需要对应版本使用.
关于更多 SignalR 内容请参考文档:
小结
本文主要介绍了 ASP.NET Core 中 Mvc,WebAPI 以及 SignalR 开发时与原来 ASP.NET 中的一些细小区别和新特性, 整体来说 ASP.NET Core 与 ASP.NET 从使用方式上基本上是一致的, 这也使得从 ASP.NET 迁移到 ASP.NET Core 变得更加容易, 但可能因为这些细小的问题往往会向代码中埋入一些坑, 所以特别编写了本文来解释这些问题.
总的来说 ASP.NET Core 的文档相当齐全, 本文中大部分内容实际都是文档中提到的, 所以建议大家在使用 ASP.NET Core 开发时, 首先第一步就是熟读文档, 避免遗漏细节. 希望本篇文章对大家有帮助(*^_^*)
参考:
本文链接: https://www.cnblogs.com/selimsong/p/10047321.html
来源: https://www.cnblogs.com/selimsong/p/10047321.html