04、NetCore2.0下Web应用之Startup源码解析

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 工程,默认使用了第一种方式,是从哪个角度考虑的???

上一篇:Linux 笔记 - 第十二章 Shell 脚本


下一篇:[转发] 理解 oauth 2.0