【春华秋实】深入源码理解.NET Core中Startup的注册及运行
写在前面
开发.NETCore应用,直接映入眼帘的就是Startup类和Program类,它们是.NETCore应用程序的起点。通过使用Startup,可以配置化处理所有向应用程序所做的请求的管道,同时也可以减少.NET应用程序对单一服务器的依赖性,使我们在更大程度上专注于面向多服务器为中心的开发模式。
目录:
- Startup讨论
- Starup所承担的角色
- Startup编写规范
- ConfigureServices
- Configure
- 扩展Startup方法
- UseStartup源码
- 创建Startup实例
- ConfigureServices和Configure
Startup讨论
Starup所承担的角色
Startup类是ASP.NETCore程序中所必须的,可以使用多种修饰符(public、protect,private、internal),作为ASP.NETCore应用程序的入口,它包含与应用程序相关配置的功能或者说是接口。
虽然在程序里我们使用的类名就是Startup,但是需要注意的是,Startup是一个抽象概念,你完全可以名称成其他的,比如MyAppStartup或者其他的什么名称,只要你在Program类中启动你所定义的启动类即可。
以下是基于ASP.NETCorePreview3模板中提供的写法:
publicclassProgram[code]{
publicstaticvoidMain(string[]args)
{
CreateHostBuilder(args).Build().Run();
}
publicstaticIHostBuilderCreateHostBuilder(string[]args)=>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder=>
{
webBuilder.UseStartup<Startup>();
});
}[/code] .csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060}
不管你命名成什么,只要将webBuilder.UseStartup<>()中的泛型类配置成你定义的入口类即可;
Startup编写规范
下面是ASP.NETCore3.0Preview3模板中Startup的写法:
//Thismethodgetscalledbytheruntime.UsethismethodtoconfiguretheHTTPrequestpipeline.[code]publicvoidConfigure(IApplicationBuilderapp,IWebHostEnvironmentenv)
{
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
//ThedefaultHSTSvalueis30days.Youmaywanttochangethisforproductionscenarios,seehttps://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting(routes=>
{
routes.MapControllers();
});
app.UseAuthorization();
}[/code] .csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060}
通过以上代码可以知道,Startup类中一般包括
.csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060}- 构造函数:通过我们以前的开发经验,我们可以知道,该构造方法可以包括多个对象 IConfiguration:表示一组键/值应用程序配置属性。
- IApplicationBuilder:是一个包含与当前环境相关的属性和方法的接口。它用于获取应用程序中的环境变量。
- IHostingEnvironment:是一个包含与运行应用程序的Web宿主环境相关信息的接口。使用这个接口方法,我们可以改变应用程序的行为。
- ILoggerFactory:是为ASP.NETCore中的日志记录系统提供配置的接口。它还创建日志系统的实例。
Startup在创建服务时,会执行依赖项注册服务,以便在应用程序的其它地方使用这些依赖项。ConfigureServices用于注册服务,Configure方法允许我们向HTTP管道添加中间件和服务。这就是ConfigureServices先于Configure之前调用的原因。
ConfigureServices
该方法时可选的,非强制约束,它主要用于对依赖注入或ApplicationServices在整个应用中的支持,该方法必须是public的,其典型模式是调用所有
Add{Service}方法,主要场景包括实体框架、认证和MVC注册服务:
services.AddDbContext<ApplicationDbContext>(options=>options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));[code]services.AddDefaultIdentity<IdentityUser>().AddDefaultUI(UIFramework.Bootstrap4).AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//Addapplicationservices.此处主要是注册IOC服务
services.AddTransient<IEmailSender,AuthMessageSender>();
services.AddTransient<ISmsSender,AuthMessageSender>();[/code]
Configure
该方法主要用于定义应用程序对每个HTTP请求的响应方式,即我们可以控制ASP.NET管道,还可用于在HTTP管道中配置中间件。请求管道中的每个中间件组件负责调用管道中的下一个组件,或在适当情况下使链发生短路。如果中间件链中未发生短路,则每个中间件都有第二次机会在将请求发送到客户端前处理该请求。
该方法接受IApplicationBuilder作为参数,同时还可以接收其他一些可选参数,如IHostingEnvironment和ILoggerFactory。
一般而言,只要将服务注册到configureServices方法中时,都可以在该方法中使用。
app.UseDeveloperExceptionPage();[code]app.UseHsts();
app.UseHttpsRedirection();
app.UseRouting(routes=>
{
routes.MapControllers();
});
app.UseAuthorization();[/code] .csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060}
扩展Startup方法
使用IStartupFilter来对Startup功能进行扩展,在应用的Configure中间件管道的开头或末尾使用IStartupFilter来配置中间件。IStartupFilter有助于确保当库在应用请求处理管道的开端或末尾添加中间件的前后运行中间件。
以下是IStartupFilter的源代码,通过源代码我们可以知道,该接口有一个Action<IApplicationBuilder>类型,并命名为Configure的方法。由于传入参数类型和返回类型一样,这就保证了扩展的传递性及顺序性,具体的演示代码,可以参数
usingSystem;
usingMicrosoft.AspNetCore.Builder;
namespaceMicrosoft.AspNetCore.Hosting
{
publicinterfaceIStartupFilter
{
Action<IApplicationBuilder>Configure(Action<IApplicationBuilder>next);
}
}.csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060}
Startup是如何注册和执行的
此段文字,只是我想深入了解其内部机制而写的,如果本身也不了解,其实是不影响我们正常编写.NETCore应用的。
UseStartup源码
ASP.NETCore通过调用IWebHostBuilder.UseStartup方法,传入Startup类型,注意开篇就已经说过Startup是一个抽象概念,我们看下源代码:
///<summary>[code]///Specifythestartuptypetobeusedbythewebhost.
///</summary>
///<paramname="hostBuilder">The<seecref="IWebHostBuilder"/>toconfigure.</param>
///<paramname="startupType">The<seecref="Type"/>tobeused.</param>
///<returns>The<seecref="IWebHostBuilder"/>.</returns>
publicstaticIWebHostBuilderUseStartup(thisIWebHostBuilderhostBuilder,TypestartupType)
{
varstartupAssemblyName=startupType.GetTypeInfo().Assembly.GetName().Name;
hostBuilder.UseSetting(WebHostDefaults.ApplicationKey,startupAssemblyName);
//LightuptheGenericWebHostBuilderimplementation
if(hostBuilderisISupportsStartupsupportsStartup)
{
returnsupportsStartup.UseStartup(startupType);
}
returnhostBuilder
.ConfigureServices(services=>
{
if(typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
{
services.AddSingleton(typeof(IStartup),startupType);
}
else
{
services.AddSingleton(typeof(IStartup),sp=>
{
varhostingEnvironment=sp.GetRequiredService<IHostEnvironment>();
returnnewConventionBasedStartup(StartupLoader.LoadMethods(sp,startupType,hostingEnvironment.EnvironmentName));
});
}
});
}
///<summary>
///Specifythestartuptypetobeusedbythewebhost.
///</summary>
///<paramname="hostBuilder">The<seecref="IWebHostBuilder"/>toconfigure.</param>
///<typeparamname="TStartup">Thetypecontainingthestartupmethodsfortheapplication.</typeparam>
///<returns>The<seecref="IWebHostBuilder"/>.</returns>
publicstaticIWebHostBuilderUseStartup<TStartup>(thisIWebHostBuilderhostBuilder)whereTStartup:class
{
returnhostBuilder.UseStartup(typeof(TStartup));
}[/code] .csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060}
创建Startup实例
///<summary>[code]///Addsadelegateforconfiguringadditionalservicesforthehostorwebapplication.Thismaybecalled
///multipletimes.
///</summary>
///<paramname="configureServices">Adelegateforconfiguringthe<seecref="IServiceCollection"/>.</param>
///<returns>The<seecref="IWebHostBuilder"/>.</returns>
publicIWebHostBuilderConfigureServices(Action<IServiceCollection>configureServices)
{
if(configureServices==null)
{
thrownewArgumentNullException(nameof(configureServices));
}
returnConfigureServices((_,services)=>configureServices(services));
}
///<summary>
///Addsadelegateforconfiguringadditionalservicesforthehostorwebapplication.Thismaybecalled
///multipletimes.
///</summary>
///<paramname="configureServices">Adelegateforconfiguringthe<seecref="IServiceCollection"/>.</param>
///<returns>The<seecref="IWebHostBuilder"/>.</returns>
publicIWebHostBuilderConfigureServices(Action<WebHostBuilderContext,IServiceCollection>configureServices)
{
_configureServices+=configureServices;
returnthis;
}[/code]
关于ConfigureServices的定义及注册方式,是在IWebHostBuilder.ConfigureServices实现的,同时可以注意一下25行代码,向大家说明了多次注册Startup的ConfigureServices方法时,会合并起来的根源。此处抽象委托用的也非常多。
该类里面还有Build方法,我就不贴出代码了,只需要知道,主进程在此处开始了。接下来一个比较重要的方法,是BuildCommonServices,它向当前ServiceCollection中添加一些公共框架级服务,以下是部分代码,具体代码请查看
try[code]{
varstartupType=StartupLoader.FindStartupType(_options.StartupAssembly,_hostingEnvironment.EnvironmentName);
if(typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
{
services.AddSingleton(typeof(IStartup),startupType);
}
else
{
services.AddSingleton(typeof(IStartup),sp=>
{
varhostingEnvironment=sp.GetRequiredService<IHostEnvironment>();
varmethods=StartupLoader.LoadMethods(sp,startupType,hostingEnvironment.EnvironmentName);
returnnewConventionBasedStartup(methods);
});
}
}
catch(Exceptionex)
{
varcapture=ExceptionDispatchInfo.Capture(ex);
services.AddSingleton<IStartup>(_=>
{
capture.Throw();
returnnull;
});
}[/code] .csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060}
由此可见,如果我们的Startup类直接实现IStartup,它可以并且将直接注册为IStartup的实现类型。只不过ASP.NETCore模板代码并没有实现IStartup,它更多的是一种约定,并通过DI调用委托,依此调用Startup内的构造函数还有另外两个方法。[code]同时上述代码还展示了如何创建Startup类型,就是用到了静态方法StartupLoader.LoadMethods类生成StartupMethods实例。
ConfigureServices和Configure
[/code]当WebHost初始化时,框架会去查找相应的方法,这里,我们主要查看源代码,其中的核心方法是StartupLoader.FindMethods[code]privatestaticMethodInfoFindMethod(TypestartupType,stringmethodName,stringenvironmentName,TypereturnType=null,boolrequired=true)
{
varmethodNameWithEnv=string.Format(CultureInfo.InvariantCulture,methodName,environmentName);
varmethodNameWithNoEnv=string.Format(CultureInfo.InvariantCulture,methodName,"");
varmethods=startupType.GetMethods(BindingFlags.Public|BindingFlags.Instance|BindingFlags.Static);
varselectedMethods=methods.Where(method=>method.Name.Equals(methodNameWithEnv,StringComparison.OrdinalIgnoreCase)).ToList();
if(selectedMethods.Count>1)
{
thrownewInvalidOperationException(string.Format("Havingmultipleoverloadsofmethod'{0}'isnotsupported.",methodNameWithEnv));
}
if(selectedMethods.Count==0)
{
selectedMethods=methods.Where(method=>method.Name.Equals(methodNameWithNoEnv,StringComparison.OrdinalIgnoreCase)).ToList();
if(selectedMethods.Count>1)
{
thrownewInvalidOperationException(string.Format("Havingmultipleoverloadsofmethod'{0}'isnotsupported.",methodNameWithNoEnv));
}
}
varmethodInfo=selectedMethods.FirstOrDefault();
if(methodInfo==null)
{
if(required)
{
thrownewInvalidOperationException(string.Format("Apublicmethodnamed'{0}'or'{1}'couldnotbefoundinthe'{2}'type.",
methodNameWithEnv,
methodNameWithNoEnv,
startupType.FullName));
}
returnnull;
}
if(returnType!=null&&methodInfo.ReturnType!=returnType)
{
if(required)
{
thrownewInvalidOperationException(string.Format("The'{0}'methodinthetype'{1}'musthaveareturntypeof'{2}'.",
methodInfo.Name,
startupType.FullName,
returnType.Name));
}
returnnull;
}
returnmethodInfo;
}[/code] .csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060}
它查找的第一个委托是ConfigureDelegate,该委托将用于构建应用程序的中间件管道。FindMethod完成了大部分工作,具体的代码请查看[code]我们知道,Startup的定义更多的是约定,所以会去查找Configure和ConfigureServices。当然,通过源代码我还知道,除了提供标准的“Configure”方法之外,我们还可以通过环境配置找到响应的Configure和ConfigureServices。根本来说,我们最终查找到的是ConfigureContainerDelegate。StartupLoader。此方法根据传递给它的methodName参数在Startup类中查找响应的方法。
接下来,一个比较重要的方法是LoadMethods
publicstaticStartupMethodsLoadMethods(IServiceProviderhostingServiceProvider,TypestartupType,stringenvironmentName)
{
varconfigureMethod=FindConfigureDelegate(startupType,environmentName);
varservicesMethod=FindConfigureServicesDelegate(startupType,environmentName);
varconfigureContainerMethod=FindConfigureContainerDelegate(startupType,environmentName);
objectinstance=null;
if(!configureMethod.MethodInfo.IsStatic||(servicesMethod!=null&&!servicesMethod.MethodInfo.IsStatic))
{
instance=ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider,startupType);
}
//ThetypeoftheTContainerBuilder.IfthereisnoConfigureContainermethodwecanjustuseobjectasit'snot
//goingtobeusedforanything.
vartype=configureContainerMethod.MethodInfo!=null?configureContainerMethod.GetContainerType():typeof(object);
varbuilder=(ConfigureServicesDelegateBuilder)Activator.CreateInstance(
typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type),
hostingServiceProvider,
servicesMethod,
configureContainerMethod,
instance);
returnnewStartupMethods(instance,configureMethod.Build(instance),builder.Build());
}[/code] .csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060}
该方法通过查找对应的方法,由于Startup并未在DI中注册,所以会调用GetServiceOrCreateInstance创建一个Startup实例,此时构造函数也在此得到解析。[code]通过一系列的调用,最终到达了ConfigureServicesBuilder.Invoke里面。Invoke方法使用反射来获取和检查在Startup类上定义的ConfigureServices方法所需的参数。
privateIServiceProviderInvokeCore(objectinstance,IServiceCollectionservices)
{
if(MethodInfo==null)
{
returnnull;
}
//OnlysupportIServiceCollectionparameters
varparameters=MethodInfo.GetParameters();
if(parameters.Length>1||
parameters.Any(p=>p.ParameterType!=typeof(IServiceCollection)))
{
thrownewInvalidOperationException("TheConfigureServicesmethodmusteitherbeparameterlessortakeonlyoneparameteroftypeIServiceCollection.");
}
vararguments=newobject[MethodInfo.GetParameters().Length];
if(parameters.Length>0)
{
arguments[0]=services;
}
returnMethodInfo.Invoke(instance,arguments)asIServiceProvider;
}[/code] .csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060} .csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060}
最后我们来看一下ConfigureBuilder类,它需要一个Action<IApplicationBuilder>委托变量,其中包含每个IStartupFilter的一组包装的Configure方法,最后一个是Startup.Configure方法的委托。此时,所调用的配置链首先命中的是AutoRequestServicesStartupFilter.Configure方法。并将该委托链作为下一个操作,之后会调用ConventionBasedStartup.Configure方法。这将在其本地StartupMethods对象上调用ConfigureDelegate。
privatevoidInvoke(objectinstance,IApplicationBuilderbuilder)[code]{
//CreateascopeforConfigure,thisallowscreatingscopeddependencies
//withoutthehassleofmanuallycreatingascope.
using(varscope=builder.ApplicationServices.CreateScope())
{
varserviceProvider=scope.ServiceProvider;
varparameterInfos=MethodInfo.GetParameters();
varparameters=newobject[parameterInfos.Length];
for(varindex=0;index<parameterInfos.Length;index++)
{
varparameterInfo=parameterInfos[index];
if(parameterInfo.ParameterType==typeof(IApplicationBuilder))
{
parameters[index]=builder;
}
else
{
try
{
parameters[index]=serviceProvider.GetRequiredService(parameterInfo.ParameterType);
}
catch(Exceptionex)
{
thrownewException(string.Format(
"Couldnotresolveaserviceoftype'{0}'fortheparameter'{1}'ofmethod'{2}'ontype'{3}'.",
parameterInfo.ParameterType.FullName,
parameterInfo.Name,
MethodInfo.Name,
MethodInfo.DeclaringType.FullName),ex);
}
}
}
MethodInfo.Invoke(instance,parameters);
}
}[/code] .csharpcode,.csharpcodepre{font-size:small;color:black;font-family:consolas,"CourierNew",courier,monospace;background-color:#ffffff} .csharpcodepre{margin:0em} .csharpcode.rem{color:#008000} .csharpcode.kwrd{color:#0000ff} .csharpcode.str{color:#006080} .csharpcode.op{color:#0000c0} .csharpcode.preproc{color:#cc6633} .csharpcode.asp{background-color:#ffff00} .csharpcode.html{color:#800000} .csharpcode.attr{color:#ff0000} .csharpcode.alt{background-color:#f4f4f4;width:100%;margin:0em} .csharpcode.lnum{color:#606060}
Startup.Configure方法会调用ServiceProvider所解析的相应的参数,该方法还可以使用IApplicationBuilder将中间件添加到应用程序管道中。最终的RequestDelegate是从IApplicationBuilder构建并返回的,至此WebHost初始化完成。
- 分析CSLA.Net 4.* 开源框架的源码,深入理解框架内部运行机制
- 深入理解Spark 2.1 Core (五):Standalone模式运行的原理与源码分析
- 深入理解Spark 2.1 Core (六):Standalone模式运行的原理与源码分析
- 深入理解计算机系统中网络编程一节echo客户端服务器的源码编译和运行
- 深入理解Spark 2.1 Core (五):Standalone模式运行的原理与源码分析
- 深入理解OkHttp源码(三)——网络操作
- Android 源码系列之<四>从源码的角度深入理解LayoutInflater.Factory之主题切换(上)
- 深入理解IIS运行时的身份
- JDK源码走读之深入理解线程池(ThreadPoolExecutor)
- 从源码角度深入理解Handler
- 深入理解Feign之源码解析
- 深入理解Spark 2.1 Core (四):运算结果处理和容错的原理与源码分析
- 深入理解Linux网络技术内幕——设备的注册与初始化(一)
- 深入理解Linux网络技术内幕-设备注册和初始化(一)
- [.NET源码] 菜鸟翻译:国外的一个关于.net core的学习系列 第一天(安装并运行.NET core 到windox系统里面)
- 深入理解Linux网络技术内幕-设备注册和初始化(四)
- 深入理解linux源码安装三板斧
- 深入理解 runtime(运行时)机制—— 整理
- 深入理解init_1----init分析(基于Android 2.2,源码来自Google)
- Java程序员从笨鸟到菜鸟之(八十二)细谈Spring(十一)深入理解spring+struts2整合(附源码)