.NET Core开发日志——Startup

一个典型的ASP.NET Core应用程序会包含Program与Startup两个文件。Program类中有应用程序的入口方法Main,其中的处理逻辑通常是创建一个WebHostBuilder,再生成WebHost,最后启动之。

而在创建WebHostBuilder时又会常常会指定一个Startup类型。

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>();

这个Startup类里究竟做了哪些事情呢?

查一下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));
                });
            }
        });
}

可以看出所指定的Startup类型会在DI容器中注册为单例形式,注册的处理过程被封装成Action

同时Startup类型可以有两种实现方式:

  • 自定义实现IStartup接口的类
  • 内部定义的ConventionBasedStartup类

实际使用的Startup类经常是这样的:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        ...
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ...
    }
}

所以明显不是第一种方式,那么其又是怎么与第二种方式关联起来的?

ConventionBasedStartup的构造方法中传入了StartupMethods类型的参数,它的LoadMethod方法里曝露了更多的信息。

public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
{
    var configureMethod = FindConfigureDelegate(startupType, environmentName);

    var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName);
    var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName);

    object instance = null;
    if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic))
    {
        instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType);
    }

    // The type of the TContainerBuilder. If there is no ConfigureContainer method we can just use object as it‘s not
    // going to be used for anything.
    var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object);

    var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance(
        typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type),
        hostingServiceProvider,
        servicesMethod,
        configureContainerMethod,
        instance);

    return new StartupMethods(instance, configureMethod.Build(instance), builder.Build());
}

它的内部处理中会找寻三种方法,ConfigureServices, Configure与ConfigureContainer。

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

private static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName)
{
    var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false);
    return new ConfigureContainerBuilder(configureMethod);
}

private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName)
{
    var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)
        ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);
    return new ConfigureServicesBuilder(servicesMethod);
}

ConfigureServices方法用于在容器中注册各种所需使用的服务接口类型,Configure方法中可以使用各种middleware(中间件),对HTTP请求pipeline(管道)进行配置。

这二者与IStartup接口的方法基本一致,而ConfigureContainer方法则是提供了一种引入第三方DI容器的功能。

public interface IStartup
{
    IServiceProvider ConfigureServices(IServiceCollection services);

    void Configure(IApplicationBuilder app);
}

使用第二种Startup类型的实现方式时,对于Configure方法的参数也可以更加灵活,不仅可以传入IApplicationBuilder类型,还可以有其它已注册过的任意接口类型。

ConfigureBuilder类的Build方法对额外参数的处理方法简单明了,是IApplicationBuilder类型的直接传入参数实例,不是的则从DI容器中获取实例:

public class ConfigureBuilder
{
    public Action<IApplicationBuilder> Build(object instance) => builder => Invoke(instance, builder);

    private void Invoke(object instance, IApplicationBuilder builder)
    {
        // Create a scope for Configure, this allows creating scoped dependencies
        // without the hassle of manually creating a scope.
        using (var scope = builder.ApplicationServices.CreateScope())
        {
            var serviceProvider = scope.ServiceProvider;
            var parameterInfos = MethodInfo.GetParameters();
            var parameters = new object[parameterInfos.Length];
            for (var index = 0; index < parameterInfos.Length; index++)
            {
                var parameterInfo = parameterInfos[index];
                if (parameterInfo.ParameterType == typeof(IApplicationBuilder))
                {
                    parameters[index] = builder;
                }
                else
                {
                    try
                    {
                        parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType);
                    }
                    ...
                }
            }
            MethodInfo.Invoke(instance, parameters);
        }
    }
}

此外,在找寻各种方法的处理中可以看到环境变量的身影。所以用第二种方式可以依据不同的环境变量定义同类型但不同名称的方法,这样可以省去写不少if...else...的处理。

UseStartup方法中还只是申明了需要注册Startup类型,实际的调用是在WebHostBuilder类执行Build方法时发生的。

private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
    ...

    foreach (var configureServices in _configureServicesDelegates)
    {
        configureServices(_context, services);
    }

    return services;
}

至于Startup类型中方法的调用时机,则需跟踪到WebHost类中。

首先是它的Initialize方法会确保获得Startup类的实例,并调用ConfigureServices方法注册服务接口。

private void EnsureApplicationServices()
{
    if (_applicationServices == null)
    {
        EnsureStartup();
        _applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
    }
}

private void EnsureStartup()
{
    if (_startup != null)
    {
        return;
    }

    _startup = _hostingServiceProvider.GetService<IStartup>();

    ...
}

然后WebHost实例被启动时,它的BuildApplication方法会创建一个ApplicationBuilder实例,以其作为Configure方法参数,同时调用Configure方法,这时Configure方法中对那些middleware的处理方式(即Func<RequestDelegate, RequestDelegate>方法)会被加入到ApplicationBuilder之中。

private RequestDelegate BuildApplication()
{
    try
    {
        _applicationServicesException?.Throw();
        EnsureServer();

        var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
        var builder = builderFactory.CreateBuilder(Server.Features);
        builder.ApplicationServices = _applicationServices;

        var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
        Action<IApplicationBuilder> configure = _startup.Configure;
        foreach (var filter in startupFilters.Reverse())
        {
            configure = filter.Configure(configure);
        }

        configure(builder);

        return builder.Build();
    }
    ...
}

ApplicationBuilder的Build方法将这些处理逻辑嵌套起来,最底层的是返回404未找到的处理逻辑。

public RequestDelegate Build()
{
    RequestDelegate app = context =>
    {
        context.Response.StatusCode = 404;
        return Task.CompletedTask;
    };

    foreach (var component in _components.Reverse())
    {
        app = component(app);
    }

    return app;
}

BuildApplication方法返回已嵌套的RequestDelegate委托方法,并在之后生成的HostingApplication实例中,将其传入它的构造方法。

public virtual async Task StartAsync(CancellationToken cancellationToken = default)
{
    HostingEventSource.Log.HostStart();
    _logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
    _logger.Starting();

    var application = BuildApplication();

    _applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
    _hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
    var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
    var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
    var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
    await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);

    ...
}

上述方法中HostingApplication实例最终被传入KestrelServer的启动方法中,这样才能在其内部调用HostingApplication的ProcessRequestAsync方法,并开始层层调用诸多的RequestDelegate方法。

public Task ProcessRequestAsync(Context context)
{
    return _application(context.HttpContext);
}

如果觉得使用Startup类还是有点麻烦的话,直接使用WebHostBuilder所提供的扩展方法也是同样的效果。

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            HostingEnvironment = hostingContext.HostingEnvironment;
            Configuration = config.Build();
        })
        .ConfigureServices(services =>
        {
            services.AddMvc();
        })
        .Configure(app =>
        {
            if (HostingEnvironment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseMvcWithDefaultRoute();
            app.UseStaticFiles();
        });

不过使用独立的Startup类有着额外的好处,Startup类可以被包含在与Program类不用的的程序集中。然后通过WebHostOptions类中StartupAssembly属性的设定及其它相关处理,完成UseStartup方法同样的功能。

private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
    ...

    if (!string.IsNullOrEmpty(_options.StartupAssembly))
    {
        try
        {
            var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName);

            if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
            {
                services.AddSingleton(typeof(IStartup), startupType);
            }
            else
            {
                services.AddSingleton(typeof(IStartup), sp =>
                {
                    var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
                    var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
                    return new ConventionBasedStartup(methods);
                });
            }
        }
        ...
    }

    ...

    return services;
}

原文地址:https://www.cnblogs.com/kenwoo/p/9381106.html

时间: 2024-08-30 13:45:30

.NET Core开发日志——Startup的相关文章

.NET Core开发日志——RequestDelegate

本文主要是对.NET Core开发日志--Middleware的补遗,但是会从看起来平平无奇的RequestDelegate开始叙述,所以以其作为标题,也是合情合理. RequestDelegate是一种委托类型,其全貌为public delegate Task RequestDelegate(HttpContext context),MSDN上对它的解释,"A function that can process an HTTP request."--处理HTTP请求的函数.唯一参数,

.NET Core开发日志——Entity Framework与PostgreSQL

Entity Framework在.NET Core中被命名为Entity Framework Core.虽然一般会用于对SQL Server数据库进行数据操作,但其实它还支持其它数据库,这里就以PostgreSQL作为例子. PostgreSQL PostgreSQL可以选用原生系统与Docker两种安装方式. Official Docker Package 在应用程序工程中添加相关的引用. dotnet add package Npgsql.EntityFrameworkCore.Postg

.Net Core开发日志——从搭建开发环境开始

.Net Core自2016年推出1.0版本开始,到目前已是2.1版本,在其roadmap计划里明年更会推出3.0版本,发展不可不谓之迅捷.不少公司在经过一个谨慎的观望期后,也逐步开始将系统升级至最新的.Net Core平台,所以现在开始进行.Net Core开发可谓正当其时. 因为.Net Core支持Windows系统以外的Linux与Mac系统,在选择开发环境时,并不需要局限在原有的Windows平台,这里我选用了Mac平台. 开发硬件设备是一台14年款的Apple Macbook Air

.Net Core开发日志——Peachpie

.Net Core的生态圈随着开源社区的力量不断注入至其中,正在变得越来越强盛,并且不时得就出现些有意思的项目,比如Peachpie,它使得PHP的代码迁移到.Net Core项目变得可能. 从创建简单的入门程序开始可以更容易地体会其特性. 首先安装Peachpie的模板: dotnet new -i Peachpie.Templates::* 接着创建项目: dotnet new web -lang PHP -o helloPHP 然后切换目录至Server文件夹运行程序: cd Server

.NET Core开发日志——从ASP.NET Core Module到KestrelServer

ASP.NET Core程序现在变得如同控制台(Console)程序一般,同样通过Main方法启动整个应用.而Main方法要做的事情很简单,创建一个WebHostBuilder类,调用其Build方法生成一个WebHost类,最后启动之. 实现代码一目了然: public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public stati

.NET Core开发日志——WCF Client

WCF作为.NET Framework3.0就被引入的用于构建面向服务的框架在众多项目中发挥着重大作用.时至今日,虽然已有更新的技术可以替代它,但对于那些既存项目或产品,使用新框架重构的代价未必能找到人愿意买单. 而在.NET Core平台环境中,WCF也并没有被完全列入迁移目标.WCF的服务端被搁置一旁,只有客户端已被移植入.NET Core之中. 这意味着,如果有需求在.NET Core中,尤其是非Windows系统环境,调用现有的WCF服务,也并非一件不可能的事情. 以一个实验来证明,先建

.NET Core开发日志——依赖注入

依赖注入(DI)不是一个新的话题,它的出现是伴随着系统解耦的需要而几乎必然产生的. 在SOLID设计原则中,DIP(Dependency inversion principle)--依赖倒置,规定了"需依赖抽象,而非实现"的准则,该原则主要目的是通过引入抽象(比如接口)的方式降低模块之间的耦合性.与此原则相拟而又有所不同的是IoC(inversion of control)--控制反转设计原则.这项原则定义了应该由通用框架而非外部代码决定控制流(control flow)的概念.对控制

.NET Core开发日志——简述路由

有过ASP.NET或其它现代Web框架开发经历的开发者对路由这一名字应该不陌生.如果要用一句话解释什么是路由,可以这样形容:通过对URL的解析,指定相应的处理程序. 回忆下在Web Forms应用程序中使用路由的方式: public static void RegisterRoutes(RouteCollection routes) { routes.MapPageRoute("", "Category/{action}/{categoryName}", "

.NET Core开发日志——Model Binding

ASP.NET Core MVC中所提供的Model Binding功能简单但实用,其主要目的是将请求中包含的数据映射到action的方法参数中.这样就避免了开发者像在Web Forms时代那样需要从Request类中手动获取数据的繁锁操作,直接提高了开发效率.此功能继承自ASP.NET MVC,所以熟悉上一代框架开发的工程师,可以毫无障碍地继续享有它的便利. 本文想要探索下Model Binding相关的内容,这里先从源码中找到其发生的时机与场合. 在ControllerActionInvok