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

------------------------------------------------------------------------------------------------------------

 写在前面:这是一个系列的文章,总目录请移步:NetCore2.0技术文章目录

------------------------------------------------------------------------------------------------------------

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

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

相关文章:

  • 2021-08-01
  • 2022-02-05
  • 2021-09-21
  • 2022-12-23
  • 2021-09-27
  • 2021-06-02
  • 2021-12-07
  • 2021-09-15
猜你喜欢
  • 2022-03-01
  • 2022-12-23
  • 2021-06-25
  • 2022-02-04
  • 2022-12-23
  • 2022-12-23
  • 2021-06-11
相关资源
相似解决方案