您的位置:首页 > 其它

04、NetCore2.0网页应用程序之Startup源码解析

2017-08-23 15:57 393 查看
04、NetCore2.0网页应用程序之Startup源码解析

 

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

 

在上一篇中,我们一步步搭建了自己的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类正是继承了[b]IStartup。[/b]





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;
}
}
}


View Code
 二、框架如何包装我们的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 "1":
res = "one";
break;
case "2":
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();
}
}
}


View Code
我们把这个类传给框架

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 "1":
res = "one";
break;
case "2":
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);
});
}
}
}


View Code
我们把这个类传给框架

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 工程,默认使用了第一种方式,是从哪个角度考虑的???
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: