aspnetcore源码学习(一)

摘要:
现在有相当多的在线相关学习材料。作者还利用现在不忙的优势学习aspnetcore的源代码。文章中使用的源代码版本为3.0。如果阅读器下的源代码低于3.0,则某些功能将有所不同。对象介绍WebHostBuilder:负责初始化环境变量、默认设置、指定启动类、创建服务集合、读取配置、创建基本服务并将其注入DI、加载主机配置,最重要的是创建webhost。

---恢复内容开始---

笔者从事netcore相关项目开发已经大半年了,从netcore 1.0到现在3.0大概经过了3年左右的时间,记得netcore刚出来的时候国内相关的学习资料缺乏,限制于外语不大熟练的限制国外的相关书籍看起来相当吃力,于是在当当网上买了一本价值70多的入门书籍,买回来却发现内容都是挂羊头卖狗肉,深深地鄙视这些为了赚钱不顾内容的作者。如今网上相关的学习资料也相当多,笔者也趁着现在不忙,再来学习一下aspnetcore的源码,文章中所用的源码版本是3.0,如果读者下的源码是3.0以下,有些函数会有所区别。

下图是笔者整理的一个简单类图,以助自己理解源码。

aspnetcore源码学习(一)第1张

对象介绍

WebHostBuilder:负责初始化环境变量,默认设置,指定startup类,创建servicecollection,读取configuration,创建基础服务并注入到DI,加载主机配置(hostingStartup),最主要的创建webhost。

WebHost:站点主机,加载应用服务,加载应用中间件,开始和停止站点

WebHostBuilderContext: 上下文,包含环境变量和默认值

WebHostOptions: 创建webhost的时候使用的参数

ServiceCollection: 所有服务的储存的集合,添加删除服务

serviceprovider: 获得服务的实例

serviceDescriptor: 服务的描述类,所有的服务最后都是转化成该类后注入DI

关键的对象介绍完了,下面我们来看一下web站点是如何运行起来的。

aspnetcore源码学习(一)第2张

  •  main函数中创建WebHostBuilder对象
  • 指定startup 

     

 public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
        {
            var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;

            hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);

            // Light up the GenericWebHostBuilder implementation
            if (hostBuilder is ISupportsStartup supportsStartup)
            {
                return supportsStartup.UseStartup(startupType);
            }

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

  

从代码可以看到,如果这个startup是继承自IStartup,直接注入到DI,如果不是,则用ConventtionBaseStartup进行包装,而该类是继承自IStartup。微软默认的startup类不继承IStartup的,具体的实现细节和原因在后面会具体说明。

  • 加载系统的默认的配置文件,可以通过 ConfigureAppConfiguration扩展方法将自己的配置文件加载到容器里面
 public static IWebHostBuilder ConfigureAppConfiguration(this IWebHostBuilder hostBuilder, Action<IConfigurationBuilder> configureDelegate)
        {
            return hostBuilder.ConfigureAppConfiguration((context, builder) => configureDelegate(builder));
        }

  

  • 创建WebHostBuild上下文
  • 加载web主机配置,寻找程序集中贴有HostingStartupAttribute标签的类并调用Configure方法,在WebHost实例化之前预留的钩子,由于笔者也没有用到过这个功能,就不多赘述了。
 foreach (var assemblyName in _options.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase))
                {
                    try
                    {
                        var assembly = Assembly.Load(new AssemblyName(assemblyName));

                        foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
                        {
                            var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
                            hostingStartup.Configure(this);
                        }
                    }
                    catch (Exception ex)
                    {
                       ...
                    }
                }
  • 创建servicecollection 类,此类存在于整个webhost生命周期,所有的应用服务和系统服务都存储在该类中
  • 添加系统服务,包括环境变量,日志服务,配置服务,监听服务等等
var services = new ServiceCollection();
            services.AddSingleton(_options);
            services.AddSingleton<IWebHostEnvironment>(_hostingEnvironment);
            services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
#pragma warning disable CS0618 // Type or member is obsolete
            services.AddSingleton<AspNetCore.Hosting.IHostingEnvironment>(_hostingEnvironment);
            services.AddSingleton<Extensions.Hosting.IHostingEnvironment>(_hostingEnvironment);
#pragma warning restore CS0618 // Type or member is obsolete
            services.AddSingleton(_context);

            var builder = new ConfigurationBuilder()
                .SetBasePath(_hostingEnvironment.ContentRootPath)
                .AddConfiguration(_config);

            _configureAppConfigurationBuilder?.Invoke(_context, builder);

            var configuration = builder.Build();
            services.AddSingleton<IConfiguration>(configuration);
            _context.Configuration = configuration;

            var listener = new DiagnosticListener("Microsoft.AspNetCore");
            services.AddSingleton<DiagnosticListener>(listener);
            services.AddSingleton<DiagnosticSource>(listener);

            services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
            services.AddTransient<IHttpContextFactory, DefaultHttpContextFactory>();
            services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
            services.AddOptions();
            services.AddLogging();

            services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();

  

  • 创建 ServiceProvider 类,该类依赖于ServiceCollection,并为其中的服务提供实例
  • 创建使用 ServiceProvider和 ServiceCollection类创建 webhost实例并初始化实例
    var host = new WebHost(
                applicationServices,
                hostingServiceProvider,
                _options,
                _config,
                hostingStartupErrors);
            try
            {
                host.Initialize();

              ...

                return host;
            }

  

初始化实例的时候会调用EnsureApplicationServices的方法。看到_startup.ConfigureServices了没有,对这个就是我们在startup中写的方法。

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

可是他是如何找到这个方法的呢,再看一下EnsureStartup方法,原来是实例化了我们在存在servicecollection中的startup类,前面我们说过,这个类ConventionBasedStartup封装了,所以这里获取的也ConventionBasedStartup类的实例。

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

            _startup = _hostingServiceProvider.GetService<IStartup>();
            ...
        }

  

再让我们看下ConventionBasedStartup的结构,实际上就是将该类作为一个代理来调用我们startup类中的方法

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;
            }
        }
    }
  •   主机实例已创建并且相关服务已初始化完成,这时候就可以start()了。可能你会想可是我的中间件还没加载,是的 ,start的时候就是加载中间件并创建监听,让我们来看一下代码
public virtual async Task StartAsync(CancellationToken cancellationToken = default)
        {
         ...

            var application = BuildApplication();
        ...
  


        }

  

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

                var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
                var builder = builderFactory.CreateBuilder(Server.Features);
                builder.ApplicationServices = _applicationServices;
Action<IApplicationBuilder> configure = _startup.Configure; ... configure(builder); return builder.Build(); } }

  发现代码中的_startup.Configure了吗!这个就是调用ConventionBasedStartup这个代理类的c方法,也就是我们startup中的Configure方法。这时候笔者当时也产生了一个疑问,这里的委托只有一个参数,可是在实际应用的时候都会加入很多参数,想这样public void Configure(IApplicationBuilder app, IHostingEnvironment env,IXxxx xxxx)。猜想是不是初始化ConventionBasedStartup类的时候做了什么封装,重新回到UseStartup方法

return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));

  

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

  哈哈果然 LoadMethods方法将我们写在StartUp中的方法进行了封装并创建了一个新的委托,到此我们也就明白为什么官方推荐的startup类不是继承了Istartup接口的,继承了接口的类在调用时没法将DI中的类注入到方法参数中去,需要自己去获取DI中的实例,笔者设想control类中的注入也是同样的思想实现的。现在我们的所有中间件和服务已经全部加载进入WebHost中了。

免责声明:文章转载自《aspnetcore源码学习(一)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇MySQL索引及使用详解FastAI 简介下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

【源码分析】FastJson全局配置日期格式导致@JSONField(format = "yyyy-MM-dd")注解失效

出现的问题 我全局配置的时间格式是:yyyy-MM-dd HH:mm:ss @JSONField注解配置的时间格式是:yyyy-MM-dd 最终的返回结果是:yyyy-MM-dd HH:mm:ss 问题:为啥不是以注解定义的时间格式为主呢?先说答案,后面再分析: FastJson的全局配置日期格式会导致@JSONField注解失效 使用建议: 1.若全局配...

【Linux】 源码安装make命令详解,避免踩坑

正常的编译安装/卸载: 源码的安装一般由3个步骤组成:配置(configure)、编译(make)、安装(make install)。   configure文件是一个可执行的脚本文件,它有很多选项,在待安装的源码目录下使用命令./configure –help可以输出详细的选项列表。   其中--prefix选项是配置安装目录,如果不配置该选项,安装后可...

【MyBatis源码分析】Configuration加载(上篇)

config.xml解析为org.w3c.dom.Document 本文首先来简单看一下MyBatis中将config.xml解析为org.w3c.dom.Document的流程,代码为上文的这部分: 1 static { 2 try { 3 reader = Resources.getResourceAsReader("myb...

html 网页源码解析:bs4中BeautifulSoup

from bs4 importBeautifulSoup result=requests.request("get","http://www.baidu.com")result.encoding="utf-8"print(result.text)         #获取源码soup=BeautifulSoup(result.text,"html.parse...

OSG安装编译

3D游戏开发课程需要使用OSG作为开发图形库,这里记录一下如何安装 步骤一:材料准备 a) Osg源码 当前最新版:OpenSceneGraph的3.2.1.zip 下载链接: http://www.osgchina.org/index.php?option=com_content&view=category&layout=blog&...

netcore3.0 IHost 源码解析(一)

Nuget包:以Microsoft.Extensins.Hosting开头的Nuget包 Github地址:https://github.com/dotnet/extensions/tree/master/src/Hosting 先看下几个重要的接口  IHostBuilder的实现类HostBuilder /// <summary>...