04、NetCore2.0Web应用之Startup源码解析
 
通过分析Asp.Net Core 2.0Startup部分源码,来理解插件框架的运行机制,以及掌握Startup注册的最优姿势。

------------------------------------------------------------------------------------------------------------

写在前面:这是一个系列的文章,总目录请移步:NetCore2.0技术文章目录

------------------------------------------------------------------------------------------------------------

上一篇中,我们一步步搭建了自己的Web应用程序,其中新建了一个StartUp类,只有一个Configure方法,并没有继承自任何接口,也就是说Asp.Net Core 2.0框架并没有使用接口来和开发者约定如何定制StartUp类,那么这个类是如何被框架使用的呢?
先下载Asp.Net Core 2.0的开源代码
 
一、重新看一下框架接入StartUp类的代码
using Microsoft.AspNetCore.Hosting;

namespace MyWeb
{
class Program
{
static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel() // 指定WebServer为Kestrel
.UseStartup<StartUpB>() // 配置WebHost
.Build(); host.Run(); // 启动WebHost
}
}
}

框架接入的关键代码是WebHostBuilder.UseStartup方法,我们去看一下框架源码:

public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
{
var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder
.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
.ConfigureServices(services =>
{
if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
{
services.AddSingleton(typeof(IStartup), startupType);
}
else
{
services.AddSingleton(typeof(IStartup), sp =>
{
var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
});
}
});
}

首先这是IWebHostBuilder接口的扩展类,这里有两个分支

1、如果StartUp从IStartup继承,则直接以单例的方式加入插件服务框架中。

2、如果不是从IStartup继承,则包装IStartup后,再以单例的方式加入插件服务框架中。

源码证实了ConventionBasedStartup类正是继承了IStartup。

public class ConventionBasedStartup : IStartup
{
private readonly StartupMethods _methods; public ConventionBasedStartup(StartupMethods methods)
{
_methods = methods;
} public void Configure(IApplicationBuilder app)
{
try
{
_methods.ConfigureDelegate(app);
}
catch (Exception ex)
{
if (ex is TargetInvocationException)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
} throw;
}
} public IServiceProvider ConfigureServices(IServiceCollection services)
{
try
{
return _methods.ConfigureServicesDelegate(services);
}
catch (Exception ex)
{
if (ex is TargetInvocationException)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
} throw;
}
}
}

二、框架如何包装我们的StartUp类

从源码看出关键代码是StartupLoader.LoadMethods,我们看看框架源码(省略了部分代码)

public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
{
var configureMethod = FindConfigureDelegate(startupType, environmentName);
var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName); object instance = null;
if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic))
{
instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType);
} Func<IServiceCollection, IServiceProvider> configureServices = services =>
{
return services.BuildServiceProvider();
}; return new StartupMethods(instance, configureMethod.Build(instance), configureServices);
}

我们猜测FindConfigureDelegate方法接入了我们的StartUp,源码证实了,框架通过反射拿到了我们的StartUp.Configure方法:原来是通过方法名字符串类匹配的^_^

private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
{
var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
return new ConfigureBuilder(configureMethod);
}

三、让我们的StartUp继承自IStartup

从上面分析可以看出,框架可以接入两种StartUp,

  • 一种是继承自IStartup的类
  • 另外一种是包含Configure方法的类

既然如此,我们的StartUp可不可以直接继承自IStartup呢?实验证明是可以的,代码如下:

using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; namespace MyWeb
{
class StartUpI : IStartup
{
public void Configure(IApplicationBuilder app)
{
app.Run(c => {
var req = c.Request.Path.ToString().TrimStart('/');
var res = string.Empty; switch (req)
{
case "":
res = "one";
break;
case "":
res = "two";
break;
default:
res = "none";
break;
} var mtd = string.Empty;
switch (c.Request.Method)
{
case "GET":
mtd = "请求方式: get";
break;
case "POST":
mtd = "请求方式:post";
break;
default:
mtd = "请求方式:none";
break;
} return c.Response.WriteAsync(res);
});
} public IServiceProvider ConfigureServices(IServiceCollection services)
{
return services.BuildServiceProvider();
}
}
}

我们把这个类传给框架

using Microsoft.AspNetCore.Hosting;

namespace MyWeb
{
class Program
{
static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel() // 指定WebServer为Kestrel
.UseStartup<StartUpI>() // 配置WebHost
.Build(); host.Run(); // 启动WebHost
}
}
}

然后运行程序,结果正如期待的:OK!

四、框架实现了一个继承自IStartup的抽象基类

通过查找IStartup的引用关系,发现框架实现了一个抽象基类StartupBase

public abstract class StartupBase : IStartup
{
public abstract void Configure(IApplicationBuilder app); IServiceProvider IStartup.ConfigureServices(IServiceCollection services)
{
ConfigureServices(services);
return CreateServiceProvider(services);
} public virtual void ConfigureServices(IServiceCollection services)
{
} public virtual IServiceProvider CreateServiceProvider(IServiceCollection services)
{
return services.BuildServiceProvider();
}
}

我们看到,只需要实现Configure这个抽象方法就可以完成StartUp的定制了,减少了我们的开发工作量,我们来实现一个子类:

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; namespace MyWeb
{
class StartUpB : StartupBase
{
public override void Configure(IApplicationBuilder app)
{
app.Run(c => {
var req = c.Request.Path.ToString().TrimStart('/');
var res = string.Empty; switch (req)
{
case "":
res = "one";
break;
case "":
res = "two";
break;
default:
res = "none";
break;
} var mtd = string.Empty;
switch (c.Request.Method)
{
case "GET":
mtd = "请求方式: get";
break;
case "POST":
mtd = "请求方式:post";
break;
default:
mtd = "请求方式:none";
break;
} return c.Response.WriteAsync(res);
});
}
}
}

我们把这个类传给框架

using Microsoft.AspNetCore.Hosting;

namespace MyWeb
{
class Program
{
static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel() // 指定WebServer为Kestrel
.UseStartup<StartUpB>() // 配置WebHost
.Build(); host.Run(); // 启动WebHost
}
}
}

然后运行程序,结果正如期待的:OK!

五、性能分析

至此我们弄清楚了ASP.Net Core2.0如何接入StartUp类,有三种方式:

1、自己定义个类,必须包含Configure方法

2、继承自IStartup,实现所有方法

3、继承自StartupBase抽象类,只需要实现Configure方法

我认为第三种方式相对来讲,效率更高,原因有二:

1、只需要实现一个方法,代码最少

2、不需要反射,效率更高

不知道,微软新建ASP.Net Core2.0 工程,默认使用了第一种方式,是从哪个角度考虑的???

04、NetCore2.0下Web应用之Startup源码解析的更多相关文章

  1. 03、NetCore2.0下Web应用之搭建最小框架

    03.NetCore2.0下Web应用之搭建最小框架 这里我们不使用VS2017或者CLI命令的方式创建Asp.Net Core 2.0网页应用程序,而是完全手工的一点点搭建一个Web框架,以便更好的 ...

  2. Laravel框架下路由的使用(源码解析)

    本篇文章给大家带来的内容是关于Laravel框架下路由的使用(源码解析),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 前言 我的解析文章并非深层次多领域的解析攻略.但是参考着开发文 ...

  3. abp vnext2.0核心组件之领域实体组件源码解析

    接着abp vnext2.0核心组件之模块加载组件源码解析和abp vnext2.0核心组件之.Net Core默认DI组件切换到AutoFac源码解析集合.Net Core3.1,基本环境已经完备, ...

  4. NetCore2.0下使用EF CodeFirst创建数据库

    本文所使用的VS版本:VS2017 15.3.0 首先新建一个.net core项目  取名NetCoreTask 使用模型视图控制器方式 新建Model层 在Model层下新建一个user实体类 1 ...

  5. Ubuntu 14.04 LTS 下 android 2.3.5 源码编译过程

    Ubuntu 14.04 LTS 下 android 2.3.5 源码编译过程   在新的Ubuntu 64位系统下去编译早期的安卓源码是会出现很多问题的,因为64位系统在安装完成后,很多32位的兼容 ...

  6. Feign 系列(04)Contract 源码解析

    Feign 系列(04)Contract 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/11563952.html# ...

  7. Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  8. 简单理解 OAuth 2.0 及资料收集,IdentityServer4 部分源码解析

    简单理解 OAuth 2.0 及资料收集,IdentityServer4 部分源码解析 虽然经常用 OAuth 2.0,但是原理却不曾了解,印象里觉得很简单,请求跳来跳去,今天看完相关介绍,就来捋一捋 ...

  9. solr&lucene3.6.0源码解析(一)

      本文作为系列的第一篇,主要描述的是solr3.6.0开发环境的搭建   首先我们需要从官方网站下载solr的相关文件,下载地址为http://archive.apache.org/dist/luc ...

随机推荐

  1. Java对象流的使用

    为了让对象持久化(把对象存储到本地),可以使用java的对象流处理对象,把对象的内容写到本地存储的文件中,也可以从本地文件中读取出来.也就是常说的序列化和反序列化 主要用到了ObjectInputSt ...

  2. OpenCascade Law Function

    OpenCascade Law Function eryar@163.com 1.Introduction 在OpenCASCADE的TKGeomAlgo Toolkit中提供了一个Law Packa ...

  3. Day3---------Linux操作系统目录结构

    一.Linux系统文件树状结构 "/" 根目录 "." 当前目录 .. 父目录,既上一层目录 pwd 显示当前目录路径 ls. = ls = ls/ 显示当前目 ...

  4. 上传到 App Store 时出错。

      Try this, it fixed it for me. Open Terminal and run: cd ~ mv .itmstransporter/ .old_itmstransporte ...

  5. [luogu1168]中位数_优先队列

    中位数 题目大意:输出读入的前2*k+1个数的中位数.一共有n个数,按照读入顺序. 注释:$1\le n \le 10^9$. 想法:这是优先队列的一个应用qwq.我们弄两个堆.小根堆和大根堆,保证: ...

  6. C语言博客作业--数组

    一.PTA实验作业 题目1.求整数序列中出现次数最多的数 1.本题PTA提交列表 2.设计思路 定义整形变量n,max,count分别表示整数个数,出现次数最大值,出现次数.定义循环变量i,j. 输入 ...

  7. MyGod_alpha版本测试报告

    买尬-Alpha版本测试报告 @(二手市场APP)[MyGod团队|团队项目|版本测试] 项目名称:武汉大学校园二手市场APP--买尬 软件版本:1.0.0 开发团队:MyGod 开发代表:程环宇 张 ...

  8. 201621123050 《Java程序设计》第5周学习总结

    1. 本周学习总结 1.1 写出你认为本周学习中比较重要的知识点关键词 答:接口.has-a.comparable 1.2 尝试使用思维导图将这些关键词组织起来.注:思维导图一般不需要出现过多的字. ...

  9. C语言——第十四、十五周作业

    题目 题目一:交换最小值和最大值 1.实验代码 #include<stdio.h> int main() { ; int i , n; int a[N]; int x , y; scanf ...

  10. JUnit单元测试遇到的问题及解决思路

    JUnit是Java单元测试框架,我们在对开发的系统进行单元测试的时候,也遇到了如何测试多个测试用例的问题.  背景:我们的所有测试用例都保存在Excel文件中,该文件包含测试用例和预期输出.我们希望 ...