首页 > 其他分享 >.net core web 启动过程(2)

.net core web 启动过程(2)

时间:2024-08-09 16:17:22浏览次数:13  
标签:core Configure UseStartup web app Startup context net type

.net core web 启动过程(1)中介绍了IHostStartup的执行过程,该文章主要介绍IStartup的执行过程。

最常用的配置Startup方式,通过调用webHostBuilder扩展方法UseStartup<T> 来指定。

  var host = new HostBuilder()
      .ConfigureWebHost(webHostBuilder =>
      {
          webHostBuilder
              .UseConfiguration(config)
              .UseKestrel()
              .UseStartup<StartupBlockingOnStart>();
      })
      .Build();

这里看一下WebHostBuilder.UseStartup<T>扩展方法的具体实现

 /// <summary>
 /// Specify the startup type to be used by the web host.
 /// </summary>
 /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
 /// <typeparam name ="TStartup">The type containing the startup methods for the application.</typeparam>
 /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
 public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
 {
     return hostBuilder.UseStartup(typeof(TStartup));
 }
internal sealed class GenericWebHostBuilder : WebHostBuilderBase, ISupportsStartup
{
    private object? _startupObject;
    private readonly object _startupKey = new object();

    private AggregateException? _hostingStartupErrors;
    private HostingStartupWebHostBuilder? _hostingStartupWebHostBuilder;


    public IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType)
    {
    //这里得到指定的startup类型所在的Assembly名称
        var startupAssemblyName = startupType.Assembly.GetName().Name;
     //记录在设置中,多次执行会覆盖上一次的
        UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);

     //UseStartup 允许多次调用,但是只会允许最后一次,这里和IHostStartup的机制不同,IHostStartup允许多个实现
        // UseStartup can be called multiple times. Only run the last one.
     //_startupObject是object类型,他是GenericWebHostBuilder对象中的一个私有变量,多次调用UseStartup,_startObject会被覆盖为最后一次的设置对象。
        _startupObject = startupType;

     //重点关注,UseStartup这里会把具体操作给 转化成一个Action给暂存到ConfigureSercices。稍后会在HostBuilder.Build()中执行,
        _builder.ConfigureServices((context, services) =>
        {
            // Run this delegate if the startup type matches
       //多次调用UseStartup,暂存多个Action,但是在执行的时候,这里会有一个验证,仅最后一次设置的才能通过,
            if (object.ReferenceEquals(_startupObject, startupType))
            {
                UseStartup(startupType, context, services);
            }
        });

        return this;
    }

}

看完上述代码后,需要提注意的是,虽然UseStartup具体操作被暂存了起来,但是 startupAssemblyName 和_startupObject是被立即记录起来的。

配置Startup的其他方式,通过传入startupFactory来实现动态改变startup的目的

/// <summary>
  /// Specify a factory that creates the startup instance to be used by the web host.
  /// </summary>
  /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
  /// <param name="startupFactory">A delegate that specifies a factory for the startup class.</param>
  /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
  /// <remarks>When in a trimmed app, all public methods of <typeparamref name="TStartup"/> are preserved. This should match the Startup type directly (and not a base type).</remarks>
  public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TStartup>(this IWebHostBuilder hostBuilder, Func<WebHostBuilderContext, TStartup> startupFactory) where TStartup : class
  {
     //通过传入一个Func<WebHostBuilderContext,TStartup>的startupFactory来实现动态改变Startup对象
  }

 

 // Note: This method isn't 100% compatible with trimming. It is possible for the factory to return a derived type from TStartup.
    // RequiresUnreferencedCode isn't on this method because the majority of people won't do that.
    public IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory)
    {

     //这里和useStartup(Type t)一样,都是记录startup所在的程序集
        var startupAssemblyName = startupFactory.GetMethodInfo().DeclaringType!.Assembly.GetName().Name;

        UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);
     //覆盖上次设置类型
        // Clear the startup type
        _startupObject = startupFactory;

        _builder.ConfigureServices(ConfigureStartup);

        [UnconditionalSuppressMessage("Trimmer", "IL2072", Justification = "Startup type created by factory can't be determined statically.")]
        void ConfigureStartup(HostBuilderContext context, IServiceCollection services)
        {
            // UseStartup can be called multiple times. Only run the last one.
            if (object.ReferenceEquals(_startupObject, startupFactory))
            {
                var webHostBuilderContext = GetWebHostBuilderContext(context);
                var instance = startupFactory(webHostBuilderContext) ?? throw new InvalidOperationException("The specified factory returned null startup instance.");
                UseStartup(instance.GetType(), context, services, instance);
            }
        }

        return this;
    }

 

 

 

对比两种配置方式,发现内部逻辑基本一致,最终都会暂存一个Action到HostBuilder.ConfigureServices,该action 都会有一个拦截验证,只允许最后一次配置startup的方式允许通过。 两种方式暂存的action 里面的操作都最终指向 

 private void UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, HostBuilderContext context, IServiceCollection services, object? instance = null)

那我们来看看,他们暂存的action 到具体执行的时候都做了什么事。

 

private void UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, HostBuilderContext context, IServiceCollection services, object? instance = null)
{
    var webHostBuilderContext = GetWebHostBuilderContext(context);
    var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];

    ExceptionDispatchInfo? startupError = null;
    ConfigureBuilder? configureBuilder = null;

    try
    {
     //我们UseStartup<T>和StartupFactory 他们指定的Startup具体类是不能继承IStartup接口,因为IStartup接口的ConfigureServices方法需要返回ServiceProvider
     //指定的Startup具体类的ConfigureServices是无需返回值的

        // We cannot support methods that return IServiceProvider as that is terminal and we need ConfigureServices to compose
        if (typeof(IStartup).IsAssignableFrom(startupType))
        {
            throw new NotSupportedException($"{typeof(IStartup)} isn't supported");
        }
        if (StartupLoader.HasConfigureServicesIServiceProviderDelegate(startupType, context.HostingEnvironment.EnvironmentName))
        {
            throw new NotSupportedException($"ConfigureServices returning an {typeof(IServiceProvider)} isn't supported.");
        }
     //如果使用的UseStartup<T>(Type type)这里需要创建对象,
     //如果使用的startupFactory instance是不等null的,具体可以看一下startupFactory那里的具体实现,暂存的action执行过程中会通过startupFactory创建指定的对象
        instance ??= ActivatorUtilities.CreateInstance(new HostServiceProvider(webHostBuilderContext), startupType);
        context.Properties[_startupKey] = instance;

        // Startup.ConfigureServices
        var configureServicesBuilder = StartupLoader.FindConfigureServicesDelegate(startupType, context.HostingEnvironment.EnvironmentName);
        var configureServices = configureServicesBuilder.Build(instance);
        //这里执行执行Startup.ConfigureServices方法
        configureServices(services);

        // REVIEW: We're doing this in the callback so that we have access to the hosting environment
        // Startup.ConfigureContainer
        var configureContainerBuilder = StartupLoader.FindConfigureContainerDelegate(startupType, context.HostingEnvironment.EnvironmentName);
        if (configureContainerBuilder.MethodInfo != null)
        {
            // Store the builder in the property bag
            _builder.Properties[typeof(ConfigureContainerBuilder)] = configureContainerBuilder;

            InvokeContainer(this, configureContainerBuilder);
        }

        // Resolve Configure after calling ConfigureServices and ConfigureContainer
        configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName);
    }
    catch (Exception ex) when (webHostOptions.CaptureStartupErrors)
    {
        startupError = ExceptionDispatchInfo.Capture(ex);
    }

    // Startup.Configure
    services.Configure<GenericWebHostServiceOptions>(options =>
    {
        options.ConfigureApplication = app =>
        {
            // Throw if there was any errors initializing startup
            startupError?.Throw();
        //这里执行 Startup.Configure
            // Execute Startup.Configure
            if (instance != null && configureBuilder != null)
            {
                configureBuilder.Build(instance)(app);
            }
        };
    });

    [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode",
        Justification = "There is a runtime check for ValueType startup container. It's unlikely anyone will use a ValueType here.")]
    static void InvokeContainer(GenericWebHostBuilder genericWebHostBuilder, ConfigureContainerBuilder configureContainerBuilder)
    {
        var containerType = configureContainerBuilder.GetContainerType();

        // Configure container uses MakeGenericType with the container type. MakeGenericType + struct container type requires IsDynamicCodeSupported.
        if (containerType.IsValueType && !RuntimeFeature.IsDynamicCodeSupported)
        {
            throw new InvalidOperationException("A ValueType TContainerBuilder isn't supported with AOT.");
        }

        var actionType = typeof(Action<,>).MakeGenericType(typeof(HostBuilderContext), containerType);

        // Get the private ConfigureContainer method on this type then close over the container type
        var configureCallback = typeof(GenericWebHostBuilder).GetMethod(nameof(ConfigureContainerImpl), BindingFlags.NonPublic | BindingFlags.Instance)!
                                         .MakeGenericMethod(containerType)
                                         .CreateDelegate(actionType, genericWebHostBuilder);

        // _builder.ConfigureContainer<T>(ConfigureContainer);
        typeof(IHostBuilder).GetMethod(nameof(IHostBuilder.ConfigureContainer))!
            .MakeGenericMethod(containerType)
            .InvokeWithoutWrappingExceptions(genericWebHostBuilder._builder, new object[] { configureCallback });
    }
}

根据代码中标注红色的部分可以看出Startup类中方法的执行顺序

1. void ConfigureServices(IServiceCollection services) ;

2. void ConfigureContainer(ContainerBuilder builder);

3. void Configure(IApplicationBuilder app, IWebHostEnvironment env);

看到 Startup.Configure的参数和IHostStartup.Configure区别了吗  

1. IHostStartup.Configure(IWebHostBuilder builder) 用于配置WebHostBuilder , 

2. Starup.Configure(IApplicationBuilder app, IWebHostEnvironment env)用于配置ApplicationBuilder ,配置HTTP 请求管道的中间件。

IHostStartup允许多个实现,执行顺序比较靠前,Starup允许多次UseStartup ,但是只允许执行最后一个Startup的方法

讲完上面,下面给出一个常见.net core web StartUp类

public class Startup
{
// ConfigureServices
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    }

    //ConfigureContainer 
    public void ConfigureContainer(ContainerBuilder builder)
    {
       
    }

    // Configure 是用于配置 HTTP 请求管道的地方
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

写到这里总感觉对于启动过程总是缺少了什么,比如说IServiceConllection是如何创建的,是什么时候创建的 。Startup.ConfigureContainer是做什么。他们直接是如何在上层对象中调用的。

想讲明白启动过程还需要了解一下HostBuilder中的方法。

 

.net core web 启动过程(3)-Hosting

标签:core,Configure,UseStartup,web,app,Startup,context,net,type
From: https://www.cnblogs.com/hitx/p/18350941

相关文章

  • Nuget 管理器》》 error: NU1101: 找不到包 ViewFaceCore
    error:NU1101:找不到包ViewFaceCore错误解释:NU1101错误表示NuGet无法找到名为ViewFaceCore的包。这通常意味着包不存在于指定的源中,或者包名称拼写错误。解决方法:检查包名称:确保ViewFaceCore是正确的包名,没有拼写错误。检查源:确保你的NuGet配置包含了......
  • ssm+vue基于VUE的Web购物网站的设计与开发【开题+程序+论文】-计算机毕业设计
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,电子商务已成为全球经济的重要组成部分,深刻改变了人们的消费习惯与商业模式。购物网站作为电子商务的核心载体,不仅为消费者......
  • Kubernetes对象YAML文件的基本格式详解
    简介  Kubernetes(K8s)作为云原生时代的基础设施核心,其配置文件通常采用YAML格式来定义和管理各种资源对象。YAML(YAMLAin'tMarkupLanguage)因其简洁、易读和易写的特性,在Kubernetes中得到了广泛应用。本文将详细探讨Kubernetes对象YAML文件的基本格式,重点解析GVK(Group、Ve......
  • com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: can not find lamb
    异常提示:com.baomidou.mybatisplus.core.exceptions.MybatisPlusException:cannotfindlambdacacheforthisentity 使用mockito框架做单元测试:mybatisplus使用Lambda表达式做条件查询、条件更新时会遇到mybatis拿不到缓存问题:错误1:com.baomidou.mybatisplus......
  • 基于 go-zero 框架的项目中集成 WebSocket
    WebSocket集成指南本文档描述了如何在基于go-zero框架的项目中集成WebSocket。1.安装依赖首先,安装gorilla/websocket库:gogetgithub.com/gorilla/websocket2.项目结构在项目中添加以下文件和目录:└──pkg└──websocket└──websocket.go3......
  • 医学图像分割的基准:TransUnet(用于医学图像分割的Transformer编码器)器官分割
    1、TransUnet介绍TransUnet是一种用于医学图像分割的深度学习模型。它是基于Transformer模型的图像分割方法,由AI研究公司HuggingFace在2021年提出。医学图像分割是一项重要的任务,旨在将医学图像中的不同结构和区域分离出来,以便医生可以更好地诊断和治疗疾病。传统的医学......
  • Stable Diffusion WebUI v1.10.0重大更新,支持SD3!
    Hello,大家好!前不久,SDWebUI的作者AUTOMATIC1111终于把它更新到了v1.10.0,这次不仅修复以往的一些BUG,提升了一些性能,这次还支持了SD3_medium.safetensors模型以及SD3_LoRA模型,同时还支持T5系列的encoder模型,让我们一起来看看这次更新了哪些内容。更新内容总共有87项更新:1.......
  • 零代码连接 OneNet 只需三分钟!一个安卓 APP 搞定 OneNet 物模型数据刷新与显示
    前言在物联网(IoT)开发中,快速连接设备与云平台、实现数据的实时刷新与显示,是开发者常常遇到的挑战。为此本文将展示如何在短短三分钟内,通过一个安卓APP轻松实现与OneNet的连接,并展示物模型数据。无论你是初学者还是有经验的开发者,这个简单的方法都能助你快速上手。什么......
  • .NET 8 + Blazor 多租户、模块化、DDD框架、开箱即用
    前言基于.NET8的开源项目,主要使用WebAPI+Blazor支持多租户和模块化设计,DDD构建。可以帮助我们轻松地搭建起一个功能完善的Web应用程序。除了帮助你快速构建应用程序之外,项目也可以当做学习资料。我们可以从中了解到多租户、CQRS、DDD架构、云部署、Docker容器化等等前沿技......
  • 鸿蒙 webview 实现顶部 Progress进度条
    1,先看效果 2,直接cv代码importweb_webviewfrom'@ohos.web.webview';interfacePerUrl{url:string,age:number}@Componentexportstructwebviews{controller:web_webview.WebviewController=newweb_webview.WebviewController();ports:......