写在前面
上篇文章我给大家讲解了 ASP.NET Core 的概念及为什么使用它, 接着带着你一步一步的配置了. NET Core 的开发环境并创建了一个 ASP.NET Core 的 mvc 项目, 同时又通过一个实战教你如何在页面显示一个 Content 的列表. 不知道你有没有跟着敲下代码, 千万不要做眼高手低的人哦.
这篇文章我们就会设计一些复杂的概念了, 因为要对 ASP.NET Core 的启动及运行原理, 配置文件的加载过程进行分析, 依赖注入, 控制反转等概念的讲解等.
俗话说, 授人以鱼不如授人以渔, 所以文章旨在带着大家分析源码, 让大家能知其然更能知其所以然. 为了偷懒, 继续使用上篇文章的例子了!
ASP.NET Core 启动源码解析
这部分我就带着大家一起看下 ASP.NET core 项目的运行流程吧! 顺带着了解下 ASP.NET core 的运行原理, 说的不好的话, 希望大家给以指正, 从而能够正确的帮助更多的人.
1. 首先上一下上篇文章的项目结构吧, 如下所示, 熟悉 C# 的朋友应该知道, 要找程序的入库, 那么就应该找到 Main 方法. 而 ASP.NET core 的 main 方法就在 Program.cs 文件中.
2. 打开后看到如下的代码, 我加了注释, 大伙将就看下, 下面我们来一步一步的分析
- /// <summary>
- /// Main 方法, 程序的入口方法
- /// </summary>
- /// <param name="args"></param>
- public static void Main(string[] args)
- {
- CreatewebHostBuilder(args)// 调用下面的方法, 返回一个 IWebHostBuilder 对象
- .Build()// 用上面返回的 IWebHostBuilder 对象创建一个 IWebHost
- .Run();// 运行上面创建的 IWebHost 对象从而运行我们的 Web 应用程序换句话说就是启动一个一直运行监听 http 请求的任务
- }
- public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
- WebHost.CreateDefaultBuilder(args)// 使用默认的配置信息来初始化一个新的 IWebHostBuilder 实例
- .UseStartup<Startup>();// 为 Web Host 指定了 Startup 类
3. 可以看到 ASP.NET core 程序实际上就是一个控制台程序, 运行一个 webhost 对象从而启动一个一直运行的监听 http 请求的任务. 所以我们的重点就是分析一下这个 WebHost 创建的过程:
创建 IWebHostBuilder-》创建 IWebHost-》然后运行创建的 IWebHost.
4. 这里我们从 IWebHostBuilder 的 Build 分析下创建的过程, 有兴趣的朋友可以看下, 没兴趣的朋友可以直接跳到下一个步骤继续阅读.
1. 首先到 aspnetcore 的 GitHub 开源地址 https://github.com/aspnet/AspNetCore/tree/release/2.1 上去下载源码 (我们使用的是 2.1). 然后使用 vscode 打开解压后的文件夹. 至于 vscode 如何加载文件, 你可以看我这篇文章使用 Visual Studio Code 开发. NET Core 看这篇就够了 .
2. 根据 IWebHostBuilder 的命名空间我们找到了它的实现, 路径为 src/Hosting/Hosting/src/WebHostBuilder.cs
3. 通过上面的代码我们可以看到首先是通过 BuildCommonServices 来构建一个 ServiceCollection. 为什么说这么说呢, 先让我们我们跳转到 BuidCommonServices 方法中看下吧.
可以看到, var services = new ServiceCollection(); 首先 new 一个 ServiceCollection 然后往 services 里面注入很多内容, 比如: WebHostOptions ,IHostingEnvironment ,IHttpContextFactory ,IMiddlewareFactory 等等 (其实这里已经设计到依赖注入的概念了, 先思考下吧), 然后我们在后续就可以使用了! 最后这个 BuildCommonServices 就返回了这个 services 对象.
4. 在上面的依赖注入中有一个方法, 不知道大家注意到没有, 因为我们在步骤 2 贴出的代码里面有一个 UseStartup<Startup>() 其实在上面的 BuildCommonServices 方法中也有对 IStartup 的注入的. 首先, 判断 Startup 类是否继承于 IStartup 接口, 如果是继承的, 那么就可以直接加入在 services 里面去, 如果不是继承的话, 就需要通过 ConventionBasedStartup(methods) 把 method 转换成 IStartUp 后注入到 services 里面去. 结合上面我们的代码, 貌似我们平时用的时候注入的方式都是采用后者.
5. 我们再回到 build 方法拿到了 BuildCommonServices 方法构建的 ServiceCollection 实例后, 通过 GetProviderFromFactory(hostingServices) 方法构造出了 IServiceProvider 对象. 到目前为止, IServiceCollection 和 IServiceProvider 都拿到了. 然后根据 IServiceCollection 和 IServiceProvider 对象构建 WebHost 对象. 构造了 WebHost 实例还不能直接返回, 还需要通过 Initialize 对 WebHost 实例进行初始化操作. 那我们看看在初始化函数 Initialize 中, 都做了什么事情吧.
6. 这里我们把代码导航到 src/Hosting/Hosting/src/Internal/WebHost.cs 找到 Initialize 方法. 如下图所示: 主要就是一个 EnsureApplicationServices 方法.
7. 我们继续导航查看这个方法的内容如下: 就是拿到 Startup 对象, 然后把_applicationServiceCollection 中的对象注入进去.
8. 至此我们 build 中注册的对象以及 StartUp 中注册的对象都已经加入到依赖注入容器中了, 接下来就是 Run 起来了. 这个 run 的代码在 src\Hosting\Hosting\src\WebHostExtensions.cs 中, 代码如下:
WebHost 执行 RunAsync 运行 Web 应用程序并返回一个只有在触发或关闭令牌时才完成的任务 (这里又涉及到异步编程的知识了, 咱们以后再详细讲解) . 这就是我们运行 ASP.NET Core 程序的时候, 看到的那个命令行窗口了, 如果不关闭窗口或者按 Ctrl+C 的话是无法结束的.
9. 至此启动的过程的源码分析完成了.
配置文件
上面给大家介绍了 ASP.NET Core 的启动过程, 中间牵扯到了一些依赖注入的概念. 关于依赖注入的概念呢, 我们后面再说, 这里先给大家讲解下配置文件的加载过程.
4. 打开上篇文章我们创建的项目, 并在 appsettings.JSON 里面加入如下内容:
- {
- "Logging": {
- "LogLevel": {
- "Default": "Warning"
- }
- },
- "Content": {
- "Id": 1,
- "title": "title1",
- "content": "content1",
- "status": 1,
- "add_time": "2018-11-21 16:29",
- "modify_time": null
- },
- "AllowedHosts": "*"
- }
5. 然后在 Startup 类中 ConfigureServices 中注册 TOptions 对象如下所示:
services.Configure<Content>(Configuration.GetSection("Content"));// 注册 TOption 实例对象
这段代码也就是从 appsettings.JSON 这个配置文件中的 Content 这个节点匹配到 Content 这个对象上.
6. 修改下 ContentController 这个控制器代码如下:
- private readonly Content contents;
- public ContentController(IOptions<Content> option)
- {
- contents = option.Value;
- }
- /// <summary>
- /// 首页显示
- /// </summary>
- /// <returns></returns>
- public IActionResult Index()
- {
- return View(new ContentViewModel { Contents=new List<Content> { contents} });
- }
7. 按下 F5 运行下, 然后导航到 Content 目录看到如下页面: 说明成功从 appsettings.JSON 这个文件中加载了内容. 这一切是怎么发生的呢? 下面我们就一步一步的来分析.
8. 我们回过头来看我们的 Main 方法, 发现里面有一个 CreateDefaultBuilder 方法, 就是这个方法里面为我们做了一些默认的设置, 然后加载我们的配置文件的!
9. 我们在源码里面找到 CreateDefaultBuilder 的源码 (反正我找了半天, 起初在 Hosting 下面找, 实际上在 MetaPackages 下面的), 位置在 src\MetaPackages\src\Microsoft.AspNetCore\WebHost.cs 有的人可能找不到哦, 可以看到这个方法会在 ConfigureAppConfiguration 的时候默认加载 appsetting 文件, 并做一些初始的设置, 所以我们不需要任何操作, 就能加载 appsettings 的内容了.
10. 既然知道了原理后, 我们就试着重写下这个 ConfigureAppConfiguration 然后加载我们自定义的 JSON 文件吧.
11. 鼠标右键新建一个 Content.JSON 文件, 然后输入如下的内容:
- {
- "ContentList":
- {
- "Id": 1,
- "title": "title1 from diy json",
- "content": "content1 from diy json",
- "status": 1,
- "add_time": "2018-11-21 16:29",
- "modify_time": null
- }
- }
12. 然后打开 Program.cs. 按如下代码进行改造:
- /// <summary>
- /// Main 方法, 程序的入口方法
- /// </summary>
- /// <param name="args"></param>
- public static void Main(string[] args)
- {
- CreateWebHostBuilder(args)// 调用下面的方法, 返回一个 WebHostBuilder 对象
- .Build()// 用上面返回的 WebHostBuilder 对象创建一个 WebHost
- .Run();// 运行上面创建的 WebHost 对象从而运行我们的 Web 应用程序换句话说就是启动一个一直运行监听 http 请求的任务
- }
- public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
- WebHost.CreateDefaultBuilder(args)// 使用默认的配置信息来初始化一个新的 IWebHostBuilder 实例
- .ConfigureAppConfiguration((hostingContext, config) =>
- {
- var env = hostingContext.HostingEnvironment;
- config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
- .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
- .AddJsonFile("Content.json",optional:false,reloadOnChange:false)
- .AddEnvironmentVariables();
- })
- .UseStartup<Startup>();// 为 Web Host 指定了 Startup 类
13. 然后 Startup 里面 ConfigureServices 中的代码修改如下:
14. 然后按下 F5 运行下代码吧, 如下图所示, 从我们最新添加的 JSON 文件中加载出来数据了.
15. 这里多讲一点, 传统 ASP.NET 的 Web.config 文件如果有更改的话是必须要重启站点才能使, 配置文件生效的, 但是 ASP.NET core 的配置文件是支持热更新的, 及不重启网站也能加载更新, 只需要设置一下属性即可, 如下图所示:
16. 配置文件的源码解读这块就到这里了. 下面开始依赖注入的讲解.
依赖注入与控制反转
如果大家仔细阅读文章的话, 相信已经看出来了, 我上面提到过好几次依赖注入的概念. 那么究竟什么是依赖注入呢? 下面我们就拿我们上面的 ContentController 来好好的来理解下.
依赖注入: 当一个对象 ContentController 需要另一个对象 Content 来协同完成任务的时候, 那么这个 ContentController 就对这个 Content 对象产生了依赖关系. 那么在这个 ContentController 中, 是怎么注入的呢? 就是从控制器中注入的了, 如下图所示:
从 ASP.NET 转过来的你是不是想起了之前的千篇一律的 new 对象啊. 没对象自己 new(要是女朋友也能 new 多好啊......) 当然除了单例对象, 静态哈.
这里又设计一个概念就是控制反转.
那么什么是控制反转呢? 你上面看到没有, 你自己 new 对象就是正转, 因为你自己创建自己所要使用的对象,. 那么这种不需要你自己 new 对象, 而是直接传进来就是控制反转了.(不知道比喻的恰不恰当哈)
依赖注入与控制反转你是否已经了解了呢, 喜欢思考的朋友可能会问了, 那这个构造函数里面的 IOptions<Content> option 又是怎么出来的? 这里就要引入一个容器的概念了.
什么是容器呢?
这里创建 IOptions<Content> option 这个对象的东西就是容器. 还记得上面我们分析源码的时候, IServiceCollection 里面注入了很多东西吗? 其实就是往 IServiceCollection 这个容器里面注入方法, 这样其他地方使用的时候就能自动注入了.
这就是容器的好处, 由容器来统一管理实例的创建和销毁, 你只需要关心怎么用就行了, 不需要关系怎么创建跟销毁.
当然容器创建的实例都是有生命周期的,. 下面罗列一下, 就不过多的讲解了.
Transient: 每一次访问都会创建一个新的实例
Scoped: 在同一个 Scope 内只初始化一个实例 , 可以理解为 ( 每一个 request 级别只创建一个实例, 同一个 http request 会在一个 scope 内)
Singleton : 整个应用程序生命周期以内只创建一个实例
使用的方式也很简单, 我会在接下来的课程中详细的通过实例来进行讲解! 因为现在的例子还没发演示.
总结
本文一步一步带着你先分析了 ASP.NET Core 的启动过程及运行的原理, 紧接着给你讲了配置文件的加载过程及原理, 并通过示例代码演示了如何加载自定义的配置文件, 最后引出了依赖注入以及控制反转的概念, 并通过对我们上面例子的分析来紧身对依赖注入以及控制反转的理解. 至此让你知其然更知其所以然. 对 ASP.NET Core 的原理相信你已经了然于胸了! 那么接下来让我们再准备下 dapper,vue 以及 Git 的快速入门就开始我们的 ASP.NET core cms 的实战课程吧! 还是那句话基础很重要, 基础打好, 后面才能事半功倍. 谢谢大家.
来源: https://www.jb51.net/article/159042.htm